1) Open preferences in a new tab instead of a dialog for better user experience. #6743
2) Add a search box to enable searching within the preferences tab. #2864pull/8848/head
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 115 KiB |
|
|
@ -2,24 +2,31 @@
|
|||
.. _preferences:
|
||||
|
||||
***************************
|
||||
`Preferences Dialog`:index:
|
||||
`Preferences`:index:
|
||||
***************************
|
||||
|
||||
Use options on the *Preferences* dialog to customize the behavior of the client.
|
||||
To open the *Preferences* dialog, select *Preferences* from the *File* menu or
|
||||
Use options in the *Preferences* tab to customize the behavior of the client.
|
||||
To open the *Preferences* tab, select *Preferences* from the *File* menu or
|
||||
click on the *Settings* button at the bottom left corner in case of Workspace
|
||||
layout.
|
||||
|
||||
.. image:: images/preferences_menu.png
|
||||
:alt: Preferences menu
|
||||
Header
|
||||
******
|
||||
|
||||
.. image:: images/preferences_header.png
|
||||
:alt: Preferences browser display options
|
||||
:align: center
|
||||
|
||||
The left pane of the *Preferences* dialog displays a tree control; each node of
|
||||
* Use the *Save* button to save any preference changes.
|
||||
* Use the *Reset all preferences* button to restore all preferences to their default values.
|
||||
* Use the *Help* button to open preferences help.
|
||||
* Quickly search all preferences using the *Search* box. It finds matches in both labels and
|
||||
help messages.
|
||||
|
||||
The left pane of the *Preferences* tab displays a tree control; each node of
|
||||
the tree control provides access to options that are related to the node under
|
||||
which they are displayed.
|
||||
|
||||
* Click the *Reset all preferences* button to restore all preferences to their default values.
|
||||
|
||||
The Browser Node
|
||||
****************
|
||||
|
||||
|
|
@ -27,7 +34,7 @@ Use preferences found in the *Browser* node of the tree control to personalize
|
|||
your workspace.
|
||||
|
||||
.. image:: images/preferences_browser_display.png
|
||||
:alt: Preferences dialog browser display options
|
||||
:alt: Preferences browser display options
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Display* panel to specify general display preferences:
|
||||
|
|
@ -64,7 +71,7 @@ Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
|||
main window navigation:
|
||||
|
||||
.. image:: images/preferences_browser_keyboard_shortcuts.png
|
||||
:alt: Preferences dialog browser keyboard shortcuts section
|
||||
:alt: Preferences browser keyboard shortcuts section
|
||||
:align: center
|
||||
|
||||
* The panel displays a list of keyboard shortcuts available for the main window;
|
||||
|
|
@ -75,7 +82,7 @@ Use the fields on the *Nodes* panel to select the object types that will be
|
|||
displayed in the *Browser* tree control:
|
||||
|
||||
.. image:: images/preferences_browser_nodes.png
|
||||
:alt: Preferences dialog browser nodes section
|
||||
:alt: Preferences browser nodes section
|
||||
:align: center
|
||||
|
||||
* The panel displays a list of database objects; slide the switch located next
|
||||
|
|
@ -87,7 +94,7 @@ Use the fields on the *Object Breadcrumbs* panel to change object breadcrumbs
|
|||
related settings:
|
||||
|
||||
.. image:: images/preferences_browser_breadcrumbs.png
|
||||
:alt: Preferences dialog object breadcrumbs section
|
||||
:alt: Preferences object breadcrumbs section
|
||||
:align: center
|
||||
|
||||
* Use *Enable object breadcrumbs?* to enable or disable object breadcrumbs
|
||||
|
|
@ -101,7 +108,7 @@ Use the fields on the *Processes* panel to change processes tab
|
|||
related settings:
|
||||
|
||||
.. image:: images/preferences_browser_processes.png
|
||||
:alt: Preferences dialog processes section
|
||||
:alt: Preferences processes section
|
||||
:align: center
|
||||
|
||||
* Change *Process details/logs retention days* to the number of days,
|
||||
|
|
@ -110,7 +117,7 @@ related settings:
|
|||
Use fields on the *Properties* panel to specify browser properties:
|
||||
|
||||
.. image:: images/preferences_browser_properties.png
|
||||
:alt: Preferences dialog browser properties section
|
||||
:alt: Preferences browser properties section
|
||||
:align: center
|
||||
|
||||
* Include a value in the *Count rows if estimated less than* field to perform a
|
||||
|
|
@ -124,7 +131,7 @@ Use fields on the *Properties* panel to specify browser properties:
|
|||
Use field on *Tab settings* panel to specify the tab related properties.
|
||||
|
||||
.. image:: images/preferences_browser_tab_settings.png
|
||||
:alt: Preferences dialog browser properties section
|
||||
:alt: Preferences browser properties section
|
||||
:align: center
|
||||
|
||||
* Use *Debugger tab title placeholder* field to customize the Debugger tab title.
|
||||
|
|
@ -144,7 +151,7 @@ The Dashboards Node
|
|||
Expand the *Dashboards* node to specify your dashboard display preferences.
|
||||
|
||||
.. image:: images/preferences_dashboard_display.png
|
||||
:alt: Preferences dialog dashboard display options
|
||||
:alt: Preferences dashboard display options
|
||||
:align: center
|
||||
|
||||
* Set the warning and alert threshold value to highlight the long-running
|
||||
|
|
@ -157,7 +164,7 @@ Expand the *Dashboards* node to specify your dashboard display preferences.
|
|||
dashboards.
|
||||
|
||||
.. image:: images/preferences_dashboard_refresh.png
|
||||
:alt: Preferences dialog dashboard refresh options
|
||||
:alt: Preferences dashboard refresh options
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Refresh rates* panel to specify your refersh rates
|
||||
|
|
@ -214,7 +221,7 @@ Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
|||
debugger window navigation:
|
||||
|
||||
.. image:: images/preferences_debugger_keyboard_shortcuts.png
|
||||
:alt: Preferences dialog debugger keyboard shortcuts section
|
||||
:alt: Preferences debugger keyboard shortcuts section
|
||||
:align: center
|
||||
|
||||
The ERD Tool Node
|
||||
|
|
@ -226,13 +233,13 @@ Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
|||
ERD Tool window navigation:
|
||||
|
||||
.. image:: images/preferences_erd_keyboard_shortcuts.png
|
||||
:alt: Preferences dialog erd keyboard shortcuts section
|
||||
:alt: Preferences erd keyboard shortcuts section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Options* panel to manage ERD preferences.
|
||||
|
||||
.. image:: images/preferences_erd_options.png
|
||||
:alt: Preferences dialog erd options section
|
||||
:alt: Preferences erd options section
|
||||
:align: center
|
||||
|
||||
|
||||
|
|
@ -252,13 +259,13 @@ The Graphs Node
|
|||
|
||||
Expand the *Graphs* node to specify your Graphs display preferences.
|
||||
|
||||
.. image:: images/preferences_dashboard_graphs.png
|
||||
:alt: Preferences dialog dashboard graph options
|
||||
:align: center
|
||||
|
||||
* Use the *Chart line width* field to specify the width of the lines on the
|
||||
line chart.
|
||||
|
||||
.. image:: images/preferences_dashboard_graphs.png
|
||||
:alt: Preferences dashboard graph options
|
||||
:align: center
|
||||
|
||||
* When the *Show graph data points?* switch is set to *True*, data points will
|
||||
be visible on graph lines.
|
||||
|
||||
|
|
@ -274,7 +281,7 @@ The Miscellaneous Node
|
|||
Expand the *Miscellaneous* node to specify miscellaneous display preferences.
|
||||
|
||||
.. image:: images/preferences_misc_file_downloads.png
|
||||
:alt: Preferences dialog file downloads section
|
||||
:alt: Preferences file downloads section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *File Downloads* panel to manage file downloads related preferences.
|
||||
|
|
@ -292,7 +299,7 @@ Use the fields on the *File Downloads* panel to manage file downloads related pr
|
|||
Use the fields on the *User Interface* panel to set the user interface related preferences.
|
||||
|
||||
.. image:: images/preferences_misc_user_interface.png
|
||||
:alt: Preferences dialog user interface section
|
||||
:alt: Preferences user interface section
|
||||
:align: center
|
||||
|
||||
* Use the *Language* drop-down listbox to select the display language for
|
||||
|
|
@ -331,7 +338,7 @@ Expand the *Paths* node to specify the locations of supporting utility and help
|
|||
files.
|
||||
|
||||
.. image:: images/preferences_paths_binary.png
|
||||
:alt: Preferences dialog binary path section
|
||||
:alt: Preferences binary path section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Binary paths* panel to specify the path to the directory
|
||||
|
|
@ -354,7 +361,7 @@ monitored databases:
|
|||
programs (pg_dump, pg_dumpall, pg_restore and psql) and there respective versions.
|
||||
|
||||
.. image:: images/preferences_paths_help.png
|
||||
:alt: Preferences dialog binary path help section
|
||||
:alt: Preferences binary path help section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Help* panel to specify the location of help files.
|
||||
|
|
@ -372,7 +379,7 @@ Expand the *Query Tool* node to access panels that allow you to specify your
|
|||
preferences for the Query Editor tool.
|
||||
|
||||
.. image:: images/preferences_sql_auto_completion.png
|
||||
:alt: Preferences dialog sqleditor auto completion option
|
||||
:alt: Preferences sqleditor auto completion option
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Auto Completion* panel to set the auto completion options.
|
||||
|
|
@ -384,7 +391,7 @@ Use the fields on the *Auto Completion* panel to set the auto completion options
|
|||
shown in upper case.
|
||||
|
||||
.. image:: images/preferences_sql_csv_output.png
|
||||
:alt: Preferences dialog sqleditor csv output option
|
||||
:alt: Preferences sqleditor csv output option
|
||||
:align: center
|
||||
|
||||
Use the fields on the *CSV/TXT Output* panel to control the CSV/TXT output.
|
||||
|
|
@ -399,7 +406,7 @@ Use the fields on the *CSV/TXT Output* panel to control the CSV/TXT output.
|
|||
specified string in the output file. Default is set to 'NULL'.
|
||||
|
||||
.. image:: images/preferences_sql_display.png
|
||||
:alt: Preferences dialog sqleditor display options
|
||||
:alt: Preferences sqleditor display options
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Display* panel to specify your preferences for the Query
|
||||
|
|
@ -415,7 +422,7 @@ Tool display.
|
|||
will show notifications on successful query execution.
|
||||
|
||||
.. image:: images/preferences_sql_editor.png
|
||||
:alt: Preferences dialog sqleditor editor settings
|
||||
:alt: Preferences sqleditor editor settings
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Editor* panel to change settings of the query editor.
|
||||
|
|
@ -451,7 +458,7 @@ Use the fields on the *Editor* panel to change settings of the query editor.
|
|||
highlight matched selected text.
|
||||
|
||||
.. image:: images/preferences_sql_explain.png
|
||||
:alt: Preferences dialog sqleditor explain options
|
||||
:alt: Preferences sqleditor explain options
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Explain* panel to specify the level of detail included in
|
||||
|
|
@ -480,7 +487,7 @@ a graphical EXPLAIN.
|
|||
will include extended information about the query execution plan.
|
||||
|
||||
.. image:: images/preferences_graph_visualiser.png
|
||||
:alt: Preferences dialog sqleditor graph visualiser section
|
||||
:alt: Preferences sqleditor graph visualiser section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Graph Visualiser* panel to specify the settings
|
||||
|
|
@ -490,7 +497,7 @@ related to graphs.
|
|||
be plotted on a chart.
|
||||
|
||||
.. image:: images/preferences_sql_options.png
|
||||
:alt: Preferences dialog sqleditor options section
|
||||
:alt: Preferences sqleditor options section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Options* panel to manage editor preferences.
|
||||
|
|
@ -536,7 +543,7 @@ Use the fields on the *Options* panel to manage editor preferences.
|
|||
will appear only if *Underline query at cursor?* is set to *False*.
|
||||
|
||||
.. image:: images/preferences_sql_results_grid.png
|
||||
:alt: Preferences dialog sql results grid section
|
||||
:alt: Preferences sql results grid section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Results grid* panel to specify your formatting
|
||||
|
|
@ -564,14 +571,14 @@ preferences for copied data.
|
|||
rows with alternating background colors.
|
||||
|
||||
.. image:: images/preferences_sql_keyboard_shortcuts.png
|
||||
:alt: Preferences dialog sql keyboard shortcuts section
|
||||
:alt: Preferences sql keyboard shortcuts section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
Query Tool window navigation.
|
||||
|
||||
.. image:: images/preferences_sql_formatting.png
|
||||
:alt: Preferences dialog SQL Formatting section
|
||||
:alt: Preferences SQL Formatting section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *SQL formatting* panel to specify your preferences for
|
||||
|
|
@ -624,7 +631,7 @@ The Storage Node
|
|||
Expand the *Storage* node to specify your storage preferences.
|
||||
|
||||
.. image:: images/preferences_storage_options.png
|
||||
:alt: Preferences dialog storage section
|
||||
:alt: Preferences storage section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Options* panel to specify storage preferences.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
"@mui/icons-material": "^7.1.1",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-date-pickers": "^8.5.0",
|
||||
"@nozbe/microfuzz": "^1.0.0",
|
||||
"@projectstorm/react-diagrams": "^7.0.4",
|
||||
"@simonwep/pickr": "^1.5.1",
|
||||
"@szhsin/react-menu": "^4.4.1",
|
||||
|
|
@ -148,6 +149,7 @@
|
|||
"sql-formatter": "^15.6.2",
|
||||
"uplot": "^1.6.32",
|
||||
"uplot-react": "^1.1.4",
|
||||
"use-resize-observer": "^9.1.0",
|
||||
"valid-filename": "^4.0.0",
|
||||
"vanilla-jsoneditor": "^3.3.1",
|
||||
"wkx": "^0.5.0",
|
||||
|
|
|
|||
|
|
@ -402,7 +402,6 @@ export default class ForeignKeySchema extends BaseUISchema {
|
|||
},{
|
||||
id: 'confdeltype', label: gettext('On delete'),
|
||||
type:'select', group: gettext('Action'), mode: ['edit','create'],
|
||||
select2:{allowClear: false},
|
||||
options: [
|
||||
{label: 'NO ACTION', value: 'a'},
|
||||
{label: 'RESTRICT', value: 'r'},
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default class RuleSchema extends BaseUISchema {
|
|||
controlProps: { allowClear: false },
|
||||
},
|
||||
{
|
||||
id: 'event', label: gettext('Event'), control: 'select2',
|
||||
id: 'event', label: gettext('Event'),
|
||||
group: gettext('Definition'), type: 'select',
|
||||
controlProps: { allowClear: false },
|
||||
options:[
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ class DataTypeReader:
|
|||
def parse_type_name(cls, type_name):
|
||||
"""
|
||||
Returns prase type name without length and precision
|
||||
so that we can match the end result with types in the select2.
|
||||
so that we can match the end result with types in the select.
|
||||
|
||||
Args:
|
||||
self: self
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ export default class ViewSchema extends BaseUISchema {
|
|||
type: 'select', group: gettext('Definition'),
|
||||
min_version: '90400', mode:['properties', 'create', 'edit'],
|
||||
controlProps: {
|
||||
// Set select2 option width to 100%
|
||||
allowClear: false,
|
||||
}, disabled: obj.notInSchema,
|
||||
options:[{
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ export default class SubscriptionSchema extends BaseUISchema{
|
|||
helpMessageMode: ['edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'sync', label: gettext('Synchronous commit'), control: 'select2', deps:['event'],
|
||||
id: 'sync', label: gettext('Synchronous commit'), deps:['event'],
|
||||
group: gettext('With'), type: 'select',
|
||||
helpMessage: gettext('The value of this parameter overrides the synchronous_commit setting. The default value is off.'),
|
||||
helpMessageMode: ['edit', 'create'],
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const BROWSER_PANELS = {
|
|||
DEPENDENCIES: 'id-dependencies',
|
||||
DEPENDENTS: 'id-dependents',
|
||||
PROCESSES: 'id-processes',
|
||||
PREFERENCES: 'id-preferences',
|
||||
PROCESS_DETAILS: 'id-process-details',
|
||||
EDIT_PROPERTIES: 'id-edit-properties',
|
||||
UTILITY_DIALOG: 'id-utility',
|
||||
|
|
|
|||
|
|
@ -205,12 +205,9 @@ export default class BgProcessManager {
|
|||
}
|
||||
|
||||
openProcessesPanel() {
|
||||
let processPanel = this.pgBrowser.docker.default_workspace.find(BROWSER_PANELS.PROCESSES);
|
||||
if(!processPanel) {
|
||||
pgAdmin.Browser.docker.default_workspace.openTab(processesPanelData, BROWSER_PANELS.MAIN, 'middle', true);
|
||||
} else {
|
||||
this.pgBrowser.docker.default_workspace.focus(BROWSER_PANELS.PROCESSES);
|
||||
}
|
||||
let handler = this.pgBrowser.getDockerHandler?.(BROWSER_PANELS.PROCESSES, this.pgBrowser.docker.default_workspace);
|
||||
handler.focus();
|
||||
handler.docker.openTab(processesPanelData, BROWSER_PANELS.MAIN, 'middle', true);
|
||||
}
|
||||
|
||||
registerListener(event, callback) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Resizable } from 're-resizable';
|
||||
// Import helpers from new file
|
||||
|
||||
|
||||
import PgTreeView from '../../../../static/js/PgTreeView';
|
||||
|
||||
export default function LeftTree({prefTreeData, selectedItem, setSelectedItem, filteredList}) {
|
||||
const filteredTreeData = useMemo(() => {
|
||||
const parentIds = filteredList.map((item) => item.parentId);
|
||||
const filteredTreeData = prefTreeData.reduce((retVal, category) => {
|
||||
const filteredChildren = category.children.filter((child) => parentIds.includes(child.id));
|
||||
if( filteredChildren.length > 0) {
|
||||
retVal.push({
|
||||
...category,
|
||||
children: filteredChildren,
|
||||
});
|
||||
}
|
||||
return retVal;
|
||||
}, []);
|
||||
return filteredTreeData;
|
||||
}, [prefTreeData, filteredList]);
|
||||
|
||||
useEffect(() => {
|
||||
// When the filtered list changes, we need to update the selected item
|
||||
// to the first item in the filtered tree data, if available.
|
||||
if (filteredTreeData.length > 0) {
|
||||
setSelectedItem(filteredTreeData[0]?.children[0] ?? null);
|
||||
}
|
||||
}, [filteredList.length]);
|
||||
|
||||
return (
|
||||
<Resizable className='PreferencesComponent-treeContainer'
|
||||
enable={{ top:false, right:true, bottom:false, left:false, topRight:false, bottomRight:false, bottomLeft:false, topLeft:false }}
|
||||
maxWidth='50%' minWidth='10%'
|
||||
defaultSize={{ width: '25%', height: '100%' }}
|
||||
id='treeContainer'
|
||||
>
|
||||
<PgTreeView
|
||||
idAccessor='id'
|
||||
data={filteredTreeData}
|
||||
openByDefault={true}
|
||||
disableMultiSelection={true}
|
||||
selection={selectedItem?.id}
|
||||
onFocus={(item) => {
|
||||
setSelectedItem(item.data);
|
||||
}}
|
||||
// don't need virtualization for preferences tree
|
||||
overscanCount={50}
|
||||
/>
|
||||
</Resizable>
|
||||
);
|
||||
}
|
||||
|
||||
LeftTree.propTypes = {
|
||||
prefTreeData: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
key: PropTypes.string.isRequired,
|
||||
children: PropTypes.array.isRequired,
|
||||
})).isRequired,
|
||||
selectedItem: PropTypes.object,
|
||||
setSelectedItem: PropTypes.func.isRequired,
|
||||
filteredList: PropTypes.array,
|
||||
};
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import _ from 'lodash';
|
||||
import url_for from 'sources/url_for';
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { DefaultButton, PrimaryButton } from '../../../../static/js/components/Buttons';
|
||||
import { getBinaryPathSchema } from './binary_path.ui';
|
||||
import { getBrowser } from '../../../../static/js/utils';
|
||||
import SaveSharpIcon from '@mui/icons-material/SaveSharp';
|
||||
import CloseIcon from '@mui/icons-material/CloseRounded';
|
||||
import HTMLReactParser from 'html-react-parser/lib/index';
|
||||
|
||||
|
||||
export async function reloadPgAdmin() {
|
||||
const { name: browser } = getBrowser();
|
||||
if (browser === 'Electron') {
|
||||
await window.electronUI.reloadApp();
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
export function getNoteField(node, subNode, nodeData, note = '') {
|
||||
// Check and add the note for the element.
|
||||
if (subNode.label === gettext('Nodes') && node.label === gettext('Browser')) {
|
||||
note = gettext('This settings is to Show/Hide nodes in the object explorer.');
|
||||
}
|
||||
|
||||
if (note.length > 0) {
|
||||
return [{
|
||||
id: _.uniqueId('note_') + subNode.id, // Better unique ID prefix
|
||||
type: 'note',
|
||||
text: note,
|
||||
parentId: nodeData.id,
|
||||
visible: false,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function prepareSubnodeData(node, subNode, nodeData, preferencesStore) {
|
||||
let addBinaryPathNote = false;
|
||||
let fieldItems = [];
|
||||
let fieldValues = {};
|
||||
const typeMap = {
|
||||
text: 'text',
|
||||
input: 'text',
|
||||
boolean: 'switch',
|
||||
node: 'switch',
|
||||
integer: 'numeric',
|
||||
numeric: 'numeric',
|
||||
date: 'datetimepicker',
|
||||
datetime: 'datetimepicker',
|
||||
options: 'select',
|
||||
select: 'select',
|
||||
multiline: 'multiline',
|
||||
switch: 'switch',
|
||||
keyboardshortcut: 'keyboardShortcut',
|
||||
radioModern: 'toggle',
|
||||
threshold: 'threshold',
|
||||
};
|
||||
|
||||
subNode.preferences.forEach((element) => {
|
||||
let type = typeMap[element.type] || element.type;
|
||||
let note = ''; // Initialize note for each element
|
||||
|
||||
// Ensure type is set after specific handling
|
||||
element.type = type;
|
||||
if (type === 'selectFile') {
|
||||
// Binary Path specific handling
|
||||
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; // Binary paths are managed in a collection, not directly editable here
|
||||
fieldValues[element.id] = JSON.parse(element.value);
|
||||
if (!addBinaryPathNote) { // Add note only once for binary path section
|
||||
fieldItems.push(...getNoteField(node, subNode, nodeData, note));
|
||||
addBinaryPathNote = true;
|
||||
}
|
||||
} else if (type === 'select') {
|
||||
element.controlProps = element.control_props ?? {};
|
||||
fieldValues[element.id] = element.value;
|
||||
|
||||
if (element.name === 'theme') {
|
||||
element.type = 'theme';
|
||||
element.options.forEach((opt) => {
|
||||
opt.selected = opt.value === element.value;
|
||||
opt.preview_src = opt.preview_src && url_for('static', { filename: opt.preview_src });
|
||||
});
|
||||
}
|
||||
} else if (type === 'keyboardShortcut') {
|
||||
element.type = 'keyboardShortcut';
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
|
||||
const storedValue = preferencesStore.getPreferences(node.label.toLowerCase(), element.name)?.value;
|
||||
fieldValues[element.id] = storedValue || element.value;
|
||||
} else if (type === 'threshold') {
|
||||
element.type = 'threshold';
|
||||
const _val = element.value.split('|');
|
||||
fieldValues[element.id] = { warning: _val[0], alert: _val[1] };
|
||||
} else if (subNode.label === gettext('Results grid') && node.label === gettext('Query Tool')) {
|
||||
if (element.name === 'column_data_max_width') {
|
||||
const sizeControl = subNode.preferences.find((_el) => _el.name === 'column_data_auto_resize');
|
||||
if (sizeControl) {
|
||||
element.disabled = (state) => state[sizeControl.id] !== 'by_data';
|
||||
}
|
||||
}
|
||||
element.type = type;
|
||||
fieldValues[element.id] = element.value;
|
||||
} else if (subNode.label === gettext('User Interface') && node.label === gettext('Miscellaneous')) {
|
||||
if (element.name === 'open_in_res_workspace') {
|
||||
const layoutControl = subNode.preferences.find((_el) => _el.name === 'layout');
|
||||
if (layoutControl) {
|
||||
element.disabled = (state) => state[layoutControl.id] !== 'workspace';
|
||||
}
|
||||
}
|
||||
element.type = type;
|
||||
fieldValues[element.id] = element.value;
|
||||
} else {
|
||||
fieldValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
delete element.value; // Original value is moved to fieldValues
|
||||
element.visible = false;
|
||||
element.helpMessage = element?.help_str || null;
|
||||
element.parentId = nodeData.id;
|
||||
fieldItems.push(element);
|
||||
});
|
||||
return { fieldItems, fieldValues };
|
||||
}
|
||||
|
||||
export function getCollectionValue(_metadata, value, initVals) {
|
||||
let val = value;
|
||||
if (typeof value === 'object' && value !== null) { // Ensure value is an object and not null
|
||||
const meta = _metadata[0]; // Assuming _metadata will always have at least one element relevant to the current field
|
||||
|
||||
if (meta.type === 'collection' && meta.schema) {
|
||||
if (value.changed?.[0] && 'binaryPath' in value.changed[0]) {
|
||||
const pathData = [];
|
||||
const pathVersions = value.changed.map(chValue => chValue.version);
|
||||
|
||||
initVals[meta.id].forEach((initVal) => {
|
||||
const changedIndex = pathVersions.indexOf(initVal.version);
|
||||
if (changedIndex !== -1) {
|
||||
pathData.push(value.changed[changedIndex]);
|
||||
} else {
|
||||
pathData.push(initVal);
|
||||
}
|
||||
});
|
||||
val = JSON.stringify(pathData);
|
||||
} else if (value.changed?.[0]) { // Generic collection, likely keyboard shortcut
|
||||
const changedEntry = value.changed[0];
|
||||
if ('key' in changedEntry && 'code' in changedEntry) {
|
||||
changedEntry.key = {
|
||||
'char': changedEntry.key, // Original `key` is now `char`
|
||||
'key_code': changedEntry.code, // Original `code` is now `key_code`
|
||||
};
|
||||
delete changedEntry.code; // Remove old code
|
||||
}
|
||||
val = changedEntry; // Changed to object
|
||||
}
|
||||
} else if ('warning' in value && 'alert' in value) { // Threshold type
|
||||
val = `${value.warning}|${value.alert}`;
|
||||
} else if (value.changed && value.changed.length > 0) { // Catch-all for other collections/arrays
|
||||
val = JSON.stringify(value.changed);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
'& .Alert-footer': {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '0.5rem',
|
||||
...theme.mixins.panelBorder.top,
|
||||
},
|
||||
'& .Alert-margin': {
|
||||
marginLeft: '0.25rem',
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
export function showResetPrefModal(api, pgAdmin, preferencesStore, onReset) {
|
||||
pgAdmin.Browser.notifier.showModal(
|
||||
gettext('Reset all preferences'),
|
||||
(modalClose) => {
|
||||
const handleResetClick = async (reloadNow) => {
|
||||
try {
|
||||
await api({
|
||||
url: url_for('preferences.index'),
|
||||
method: 'DELETE',
|
||||
});
|
||||
preferencesStore.cache(); // Refresh preferences cache
|
||||
onReset();
|
||||
if (reloadNow) {
|
||||
reloadPgAdmin();
|
||||
} else {
|
||||
pgAdmin.Browser.tree.destroy().then(() => {
|
||||
pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:destroyed', undefined, undefined);
|
||||
modalClose(); // Close modal after tree destruction if no full reload
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
pgAdmin.Browser.notifier.alert(err.response?.data || err.message || gettext('Failed to reset preferences.'));
|
||||
modalClose();
|
||||
}
|
||||
};
|
||||
|
||||
const text = `${gettext('All preferences will be reset to their default values.')}<br><br>${gettext('Do you want to proceed?')}<br><br>
|
||||
${gettext('Note:')}<br> <ul style="padding-left:20px"><li style="list-style-type:disc">${gettext('The object explorer tree will be refreshed automatically to reflect the changes.')}</li>
|
||||
<li style="list-style-type:disc">${gettext('If the application language changes, a reload of the application will be required. You can choose to reload later at your convenience.')}</li></ul>`;
|
||||
|
||||
return (
|
||||
<StyledBox display="flex" flexDirection="column" height="100%">
|
||||
<Box flexGrow="1" p={2}>
|
||||
{HTMLReactParser(text)}
|
||||
</Box>
|
||||
<Box className='Alert-footer'>
|
||||
<DefaultButton className='Alert-margin' startIcon={<CloseIcon />} onClick={modalClose}>
|
||||
{gettext('Cancel')}
|
||||
</DefaultButton>
|
||||
<DefaultButton className='Alert-margin' startIcon={<SaveSharpIcon />} onClick={() => handleResetClick(true)}>
|
||||
{gettext('Save & Reload')}
|
||||
</DefaultButton>
|
||||
<PrimaryButton className='Alert-margin' startIcon={<SaveSharpIcon />} onClick={() => handleResetClick(false)}>
|
||||
{gettext('Save & Reload Later')}
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</StyledBox>
|
||||
);
|
||||
},
|
||||
{ isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: true, id: 'id-reset-preferences' }
|
||||
);
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
// /////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// // pgAdmin 4 - PostgreSQL Tools
|
||||
// //
|
||||
// // Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// // This software is released under the PostgreSQL Licence
|
||||
// //
|
||||
// //////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Directory} from 'react-aspen';
|
||||
import { Tree } from '../../../../static/js/tree/tree';
|
||||
import { ManagePreferenceTreeNodes } from '../../../../static/js/tree/preference_nodes';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { FileTreeX, TreeModelX } from '../../../../static/js/components/PgTree';
|
||||
|
||||
|
||||
export default function PreferencesTree({ pgBrowser, data }) {
|
||||
const pTreeModelX = React.useRef();
|
||||
const onReadyRef = React.useRef();
|
||||
const [loaded, setLoaded] = React.useState(false);
|
||||
|
||||
const MOUNT_POINT = '/preferences';
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoaded(false);
|
||||
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
|
||||
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 == gettext('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;
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
pTreeModelX.current.root.ensureLoaded().then(() => {
|
||||
setLoaded(true);
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
if (!loaded || _.isUndefined(pTreeModelX.current) || _.isUndefined(onReadyRef.current)) {
|
||||
return (gettext('Loading...'));
|
||||
}
|
||||
return (<FileTreeX model={pTreeModelX.current} height={'100%'} onReady={onReadyRef.current} />);
|
||||
}
|
||||
|
||||
PreferencesTree.propTypes = {
|
||||
pgBrowser: PropTypes.any,
|
||||
data: PropTypes.array,
|
||||
ptree: PropTypes.any,
|
||||
};
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import { Box, Link } from '@mui/material';
|
||||
import PropTypes from 'prop-types';
|
||||
import SchemaView from '../../../../static/js/SchemaView';
|
||||
// Import helpers from new file
|
||||
|
||||
|
||||
|
||||
export default function RightPreference({ schema, filteredItemIds, selectedItem, setSelectedItem, initValues, onDataChange }) {
|
||||
const schemaViewRef = useRef(null);
|
||||
|
||||
const getInitData = useCallback(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(initValues);
|
||||
} catch (error) {
|
||||
reject(error instanceof Error ? error : Error(gettext('Something went wrong')));
|
||||
}
|
||||
});
|
||||
}, [initValues]);
|
||||
|
||||
const updateVisibleFields = () => {
|
||||
if(!selectedItem) return;
|
||||
|
||||
schema.schemaFields.forEach((field) => {
|
||||
field.visible = field.parentId === selectedItem.id && filteredItemIds.includes(field.id);
|
||||
field.labelTooltip = `${selectedItem.key.toLowerCase()}:${selectedItem.key}:${field.key}`;
|
||||
});
|
||||
schema.categoryUpdated(selectedItem.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateVisibleFields();
|
||||
}, [filteredItemIds, selectedItem]);
|
||||
|
||||
if(selectedItem?.children) {
|
||||
return (
|
||||
<Box className='PreferencesComponent-preferencesContainer'>
|
||||
<Box className='PreferencesComponent-noSelection'>
|
||||
<Box>{gettext('Navigate to any below item to view or edit its preferences.')}</Box>
|
||||
{selectedItem.children.map((child) => (
|
||||
<Box key={child.id}>
|
||||
<Link component='button' onClick={()=>setSelectedItem(child)} underline="hover">{child.name}</Link>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='PreferencesComponent-preferencesContainer' ref={schemaViewRef}>
|
||||
<SchemaView
|
||||
key={selectedItem?.id ?? 0}
|
||||
formType={'dialog'}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{ mode: 'edit' }}
|
||||
schema={schema}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
formClassName='PreferencesComponent-preferencesContainerBackground'
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
onDataChange(changedData);
|
||||
}}
|
||||
focusOnFirstInput={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
RightPreference.propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
initValues: PropTypes.object.isRequired,
|
||||
onDataChange: PropTypes.func.isRequired,
|
||||
filteredItemIds: PropTypes.arrayOf(PropTypes.any).isRequired,
|
||||
selectedItem: PropTypes.object,
|
||||
setSelectedItem: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ import gettext from 'sources/gettext';
|
|||
import _ from 'lodash';
|
||||
import url_for from 'sources/url_for';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import getApiInstance from '../../../../../static/js/api_instance';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
export function getBinaryPathSchema() {
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { BaseUISchema } from '../../../../static/js/SchemaView';
|
||||
|
||||
export default class PreferencesSchema extends BaseUISchema {
|
||||
constructor(initValues = {}, schemaFields = []) {
|
||||
super({
|
||||
...initValues
|
||||
});
|
||||
this.schemaFields = schemaFields;
|
||||
this.category = '';
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'id';
|
||||
}
|
||||
|
||||
categoryUpdated() {
|
||||
this.state?.validate(this.sessData);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return this.schemaFields;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,9 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import PreferencesComponent from './components/PreferencesComponent';
|
||||
import PreferencesTree from './components/PreferencesTree';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { BROWSER_PANELS } from '../../../browser/static/js/constants';
|
||||
import { preferencesPanelData } from '../../../static/js/BrowserComponent';
|
||||
|
||||
export default class Preferences {
|
||||
static instance;
|
||||
|
|
@ -48,13 +46,8 @@ export default class Preferences {
|
|||
|
||||
// This is a callback function to show preferences.
|
||||
show() {
|
||||
// Render Preferences component
|
||||
pgAdmin.Browser.notifier.showModal(gettext('Preferences'), (closeModal) => {
|
||||
return <PreferencesComponent
|
||||
renderTree={(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, id: 'id-preferences' });
|
||||
let handler = this.pgBrowser.getDockerHandler?.(BROWSER_PANELS.USER_MANAGEMENT, this.pgBrowser.docker.default_workspace);
|
||||
handler.focus();
|
||||
handler.docker.openTab(preferencesPanelData, BROWSER_PANELS.MAIN, 'middle', true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import pgWindow from 'sources/window';
|
|||
import WorkspaceToolbar from '../../misc/workspaces/static/js/WorkspaceToolbar';
|
||||
import { useWorkspace, WorkspaceProvider } from '../../misc/workspaces/static/js/WorkspaceProvider';
|
||||
import { PgAdminProvider, usePgAdmin } from './PgAdminProvider';
|
||||
import PreferencesComponent from '../../preferences/static/js/components/PreferencesComponent';
|
||||
|
||||
|
||||
const objectExplorerGroup = {
|
||||
|
|
@ -45,6 +46,10 @@ export const processesPanelData = {
|
|||
id: BROWSER_PANELS.PROCESSES, title: gettext('Processes'), content: <Processes />, closable: true, group: 'playground'
|
||||
};
|
||||
|
||||
export const preferencesPanelData = {
|
||||
id: BROWSER_PANELS.PREFERENCES, title: gettext('Preferences'), content: <PreferencesComponent panelId={BROWSER_PANELS.PREFERENCES} />, closable: true, manualClose: true, group: 'playground'
|
||||
};
|
||||
|
||||
export const defaultTabsData = [
|
||||
{
|
||||
id: BROWSER_PANELS.DASHBOARD, title: gettext('Dashboard'), content: <Dashboard />, closable: true, group: 'playground'
|
||||
|
|
@ -67,9 +72,11 @@ export const defaultTabsData = [
|
|||
processesPanelData,
|
||||
];
|
||||
|
||||
const mainPanelGroup = {
|
||||
...getDefaultGroup(),
|
||||
panelExtra: () => <MainMoreToolbar tabsData={defaultTabsData}/>
|
||||
const getMorePanelGroup = (tabsData) => {
|
||||
return {
|
||||
...getDefaultGroup(),
|
||||
panelExtra: () => <MainMoreToolbar tabsData={tabsData}/>
|
||||
};
|
||||
};
|
||||
|
||||
let defaultLayout = {
|
||||
|
|
@ -116,7 +123,7 @@ function Layouts({browser}) {
|
|||
savedLayout={pgAdmin.Browser.utils.layout}
|
||||
groups={{
|
||||
'object-explorer': objectExplorerGroup,
|
||||
'playground': mainPanelGroup,
|
||||
'playground': getMorePanelGroup(defaultTabsData),
|
||||
}}
|
||||
noContextGroups={['object-explorer']}
|
||||
resetToTabPanel={BROWSER_PANELS.MAIN}
|
||||
|
|
@ -132,7 +139,7 @@ function Layouts({browser}) {
|
|||
}}
|
||||
defaultLayout={item.layout}
|
||||
groups={{
|
||||
'playground': item?.tabsData ? {...getDefaultGroup(), panelExtra: () => <MainMoreToolbar tabsData={item.tabsData}/>} : {...getDefaultGroup()},
|
||||
'playground': item?.tabsData ? getMorePanelGroup(item?.tabsData) : {...getDefaultGroup()},
|
||||
}}
|
||||
resetToTabPanel={BROWSER_PANELS.MAIN}
|
||||
isLayoutVisible={currentWorkspace == item.workspace}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,74 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { Checkbox } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import gettext from 'sources/gettext';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Tree } from 'react-arborist';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import PropTypes from 'prop-types';
|
||||
import IndeterminateCheckBoxIcon from '@mui/icons-material/IndeterminateCheckBox';
|
||||
import EmptyPanelMessage from '../components/EmptyPanelMessage';
|
||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||
|
||||
import useResizeObserver from 'use-resize-observer';
|
||||
|
||||
|
||||
const Root = styled('div')(({ theme }) => ({
|
||||
height: '100%',
|
||||
'& .PgTree-tree': {
|
||||
background: theme.palette.background.default,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: theme.palette.background.default,
|
||||
width: '100%',
|
||||
'& *:focus-visible': {
|
||||
outline: 'none',
|
||||
},
|
||||
'& .PgTree-defaultNode': {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
'& .PgTree-leafNode': {
|
||||
marginLeft: '1.5rem'
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
flexWrap: 'nowrap',
|
||||
|
||||
'& .PgTree-expandSpacer': {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
flexShrink: 0,
|
||||
},
|
||||
'& .PgTree-node': {
|
||||
display: 'inline-block',
|
||||
paddingLeft: '1.5rem',
|
||||
|
||||
'& .PgTree-indentLine': {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
marginLeft: '-36px',
|
||||
borderLeft: '1px solid ' + theme.otherVars.borderColor,
|
||||
flexShrink: 0,
|
||||
},
|
||||
|
||||
'& .PgTree-nodeLabel': {
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
||||
'& .PgTree-icon': {
|
||||
display: 'inline-block',
|
||||
width: '20px',
|
||||
backgroundPosition: 'center',
|
||||
},
|
||||
|
||||
'& .no-icon': {
|
||||
display: 'none',
|
||||
paddingLeft: '0rem',
|
||||
},
|
||||
},
|
||||
'& .PgTree-focusedNode': {
|
||||
background: theme.palette.primary.light,
|
||||
},
|
||||
},
|
||||
'& .PgTree-focusedNode': {
|
||||
background: theme.palette.primary.light,
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -46,6 +82,7 @@ export default function PgTreeView({ data = [], hasCheckbox = false,
|
|||
const treeObj = useRef();
|
||||
const treeContainerRef = useRef();
|
||||
const [selectedCheckBoxNodes, setSelectedCheckBoxNodes] = React.useState([]);
|
||||
const { ref: containerRef, width, height } = useResizeObserver();
|
||||
|
||||
const onSelectionChange = () => {
|
||||
let selectedChNodes = treeObj.current.selectedNodes;
|
||||
|
|
@ -69,31 +106,27 @@ export default function PgTreeView({ data = [], hasCheckbox = false,
|
|||
selectionChange?.(selectedChNodes);
|
||||
};
|
||||
|
||||
return (<Root>
|
||||
return (<Root ref={containerRef} className={'PgTree-tree'}>
|
||||
{treeData.length > 0 ?
|
||||
<PgTreeSelectionContext.Provider value={selectedCheckBoxNodes}>
|
||||
<div ref={(containerRef) => treeContainerRef.current = containerRef} className={'PgTree-tree'}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<Tree
|
||||
ref={(obj) => {
|
||||
treeObj.current = obj;
|
||||
}}
|
||||
width={isNaN(width) ? 100 : width}
|
||||
height={isNaN(height) ? 100 : height}
|
||||
data={treeData}
|
||||
disableDrag={true}
|
||||
disableDrop={true}
|
||||
dndRootElement={treeContainerRef.current}
|
||||
{...props}
|
||||
>
|
||||
{
|
||||
(props) => <Node onNodeSelectionChange={onSelectionChange} hasCheckbox={hasCheckbox} {...props} />
|
||||
}
|
||||
</Tree>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<Tree
|
||||
ref={(obj) => {
|
||||
treeObj.current = obj;
|
||||
}}
|
||||
width={isNaN(width) ? 100 : width}
|
||||
height={isNaN(height) ? 100 : height}
|
||||
data={treeData}
|
||||
disableDrag={true}
|
||||
disableDrop={true}
|
||||
dndRootElement={treeContainerRef.current}
|
||||
selectionFollowsFocus
|
||||
{...props}
|
||||
indent={24}
|
||||
>
|
||||
{
|
||||
(props) => <Node onNodeSelectionChange={onSelectionChange} hasCheckbox={hasCheckbox} {...props} />
|
||||
}
|
||||
</Tree>
|
||||
</PgTreeSelectionContext.Provider>
|
||||
:
|
||||
<EmptyPanelMessage text={gettext('No objects are found to display')} />
|
||||
|
|
@ -109,7 +142,7 @@ PgTreeView.propTypes = {
|
|||
NodeComponent: PropTypes.func
|
||||
};
|
||||
|
||||
function DefaultNode({ node, style, tree, hasCheckbox, onNodeSelectionChange }) {
|
||||
function DefaultNode({ node, style, tree, hasCheckbox, onNodeSelectionChange, ...props }) {
|
||||
|
||||
const pgTreeSelCtx = React.useContext(PgTreeSelectionContext);
|
||||
const [isSelected, setIsSelected] = React.useState(pgTreeSelCtx.includes(node.id) || node.data?.isSelected);
|
||||
|
|
@ -163,28 +196,17 @@ function DefaultNode({ node, style, tree, hasCheckbox, onNodeSelectionChange })
|
|||
onNodeSelectionChange();
|
||||
};
|
||||
|
||||
const onSelect = (e) => {
|
||||
node.focus();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
if (e.code == 'Enter') {
|
||||
onSelect(e);
|
||||
}
|
||||
};
|
||||
|
||||
const className = `${node.isSelected ? 'PgTree-focusedNode' : ''}`;
|
||||
return (
|
||||
<div style={style} className={node.isFocused ? 'PgTree-focusedNode' : ''} onClick={onSelect} onKeyDown={onKeyDown}>
|
||||
<CollectionArrow node={node} tree={tree} selectedNodeIds={pgTreeSelCtx} />
|
||||
{
|
||||
hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={!node.isInternal ? 'PgTree-leafNode' : null}
|
||||
checked={isSelected}
|
||||
checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon style={{ height: '1.4rem' }} /> : <CheckBoxIcon style={{ height: '1.4rem' }} />}
|
||||
onChange={onCheckboxSelection} /> :
|
||||
<span className={node.data.icon}></span>
|
||||
}
|
||||
<div className={node.data.icon + ' PgTree-node'}>{node.data.name}</div>
|
||||
<div style={style} className={className} {...props}>
|
||||
<div className={'PgTree-defaultNode'}>
|
||||
<ExpandIcon node={node} tree={tree} selectedNodeIds={pgTreeSelCtx} />
|
||||
<IndentIcon node={node} hasCheckbox={hasCheckbox} isSelected={isSelected} isIndeterminate={isIndeterminate} onCheckboxSelection={onCheckboxSelection} />
|
||||
<div className='PgTree-nodeLabel'>
|
||||
<span className={`PgTree-icon ${node.data.icon || 'no-icon'}`} />
|
||||
{node.data.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -197,7 +219,31 @@ DefaultNode.propTypes = {
|
|||
onNodeSelectionChange: PropTypes.func
|
||||
};
|
||||
|
||||
function CollectionArrow({ node, tree, selectedNodeIds }) {
|
||||
function IndentIcon({node, hasCheckbox, isSelected, isIndeterminate, onCheckboxSelection}) {
|
||||
if(hasCheckbox) {
|
||||
return (
|
||||
<Checkbox style={{ padding: 0 }} color="primary"
|
||||
checked={isSelected}
|
||||
checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon style={{ height: '1.5rem' }} /> : <CheckBoxIcon style={{ height: '1.5rem' }} />}
|
||||
onChange={onCheckboxSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if(hasExpand(node)) {
|
||||
return <></>;
|
||||
}
|
||||
return <div className='PgTree-indentLine'></div>;
|
||||
}
|
||||
|
||||
IndentIcon.propTypes = {
|
||||
node: PropTypes.object,
|
||||
hasCheckbox: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
isIndeterminate: PropTypes.bool,
|
||||
onCheckboxSelection: PropTypes.func
|
||||
};
|
||||
|
||||
function ExpandIcon({ node, tree, selectedNodeIds }) {
|
||||
const toggleNode = () => {
|
||||
node.isInternal && node.toggle();
|
||||
if (node.isSelected && node.isOpen) {
|
||||
|
|
@ -205,14 +251,17 @@ function CollectionArrow({ node, tree, selectedNodeIds }) {
|
|||
selectAllChild(node, tree, 'expand', selectedNodeIds);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<span onClick={toggleNode} onKeyDown={() => {/* handled by parent */ }}>
|
||||
{node.isInternal && node?.children.length > 0 ? <ToggleArrowIcon node={node} /> : null}
|
||||
</span>
|
||||
);
|
||||
if(hasExpand(node)) {
|
||||
return (
|
||||
<span onClick={toggleNode} onKeyDown={() => {/* handled by parent */ }}>
|
||||
{node.isOpen ? <ExpandMoreIcon /> : <ChevronRightIcon />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (<div className='PgTree-expandSpacer'></div>);
|
||||
}
|
||||
|
||||
CollectionArrow.propTypes = {
|
||||
ExpandIcon.propTypes = {
|
||||
node: PropTypes.object,
|
||||
tree: PropTypes.object,
|
||||
selectedNodeIds: PropTypes.array
|
||||
|
|
@ -251,9 +300,9 @@ function checkAndSelectParent(chNode) {
|
|||
}
|
||||
}
|
||||
|
||||
checkAndSelectParent.propTypes = {
|
||||
chNode: PropTypes.object
|
||||
};
|
||||
function hasExpand(node) {
|
||||
return node.isInternal && node?.children.length > 0;
|
||||
}
|
||||
|
||||
function delectPrentNode(chNode) {
|
||||
if (chNode) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ import { WORKSPACES } from '../../../browser/static/js/constants';
|
|||
/* If its the dialog */
|
||||
export default function SchemaDialogView({
|
||||
getInitData, viewHelperProps, loadingText, schema={}, showFooter=true,
|
||||
isTabView=true, checkDirtyOnEnableSave=false, customCloseBtnName=gettext('Close'), ...props
|
||||
isTabView=true, checkDirtyOnEnableSave=false, customCloseBtnName=gettext('Close'), focusOnFirstInput=true,
|
||||
...props
|
||||
}) {
|
||||
// View helper properties
|
||||
const onDataChange = props.onDataChange;
|
||||
|
|
@ -190,7 +191,7 @@ export default function SchemaDialogView({
|
|||
isTabView={isTabView}
|
||||
className={props.formClassName}
|
||||
showError={true} resetKey={resetKey}
|
||||
focusOnFirstInput={true}
|
||||
focusOnFirstInput={focusOnFirstInput}
|
||||
/>
|
||||
</Box>
|
||||
{showFooter &&
|
||||
|
|
@ -268,4 +269,5 @@ SchemaDialogView.propTypes = {
|
|||
Notifier: PropTypes.object,
|
||||
checkDirtyOnEnableSave: PropTypes.bool,
|
||||
customCloseBtnName: PropTypes.string,
|
||||
focusOnFirstInput: PropTypes.bool,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { Button, ButtonGroup, Tooltip } from '@mui/material';
|
||||
import React, { forwardRef } from 'react';
|
||||
import React, { forwardRef, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import ShortcutTitle from './ShortcutTitle';
|
||||
|
|
@ -174,20 +174,37 @@ DefaultButton.propTypes = {
|
|||
|
||||
|
||||
/* pgAdmin Icon button, takes Icon component as input */
|
||||
export const PgIconButton = forwardRef(({icon, title, shortcut, className, splitButton, style, color, accesskey, isDropdown, tooltipPlacement, ...props}, ref)=>{
|
||||
export const PgIconButton = forwardRef(({icon, title, shortcut, className, splitButton, style, color, isDropdown, tooltipPlacement, ...props}, ref)=>{
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
let shortcutTitle = null;
|
||||
if(accesskey || shortcut) {
|
||||
shortcutTitle = <ShortcutTitle title={title} accesskey={accesskey} shortcut={shortcut}/>;
|
||||
if(shortcut) {
|
||||
shortcutTitle = <ShortcutTitle title={title} shortcut={shortcut}/>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// If the button is disabled changes, we should close the tooltip
|
||||
// as the old button is unmounted.
|
||||
setTooltipOpen(false);
|
||||
}, [props.disabled]);
|
||||
|
||||
const tooltipProps = {
|
||||
title: shortcutTitle || title || '',
|
||||
'aria-label': title || '',
|
||||
open: tooltipOpen,
|
||||
onOpen: () => setTooltipOpen(true),
|
||||
onClose: () => setTooltipOpen(false),
|
||||
enterDelay: isDropdown ? 1500 : undefined,
|
||||
placement: tooltipPlacement,
|
||||
};
|
||||
|
||||
if(props.disabled) {
|
||||
if(color == 'primary') {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined} placement={tooltipPlacement}>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<span>
|
||||
<PrimaryButton ref={ref} style={style}
|
||||
className={['Buttons-iconButton', (splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
|
||||
accessKey={accesskey} data-label={title || ''} {...props}>
|
||||
data-label={title || ''} {...props}>
|
||||
{icon}
|
||||
</PrimaryButton>
|
||||
</span>
|
||||
|
|
@ -195,11 +212,11 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined} placement={tooltipPlacement}>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<span>
|
||||
<DefaultButton ref={ref} style={style}
|
||||
className={['Buttons-iconButton', 'Buttons-iconButtonDefault',(splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
|
||||
accessKey={accesskey} data-label={title || ''} {...props}>
|
||||
data-label={title || ''} {...props}>
|
||||
{icon}
|
||||
</DefaultButton>
|
||||
</span>
|
||||
|
|
@ -208,10 +225,10 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
|
|||
}
|
||||
} else if(color == 'primary') {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined} placement={tooltipPlacement}>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<PrimaryButton ref={ref} style={style}
|
||||
className={['Buttons-iconButton', (splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
|
||||
accessKey={accesskey} data-label={title || ''} {...props}>
|
||||
data-label={title || ''} {...props}>
|
||||
{icon}
|
||||
</PrimaryButton>
|
||||
</Tooltip>
|
||||
|
|
@ -219,10 +236,10 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined} placement={tooltipPlacement}>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<DefaultButton ref={ref} style={style}
|
||||
className={['Buttons-iconButton', 'Buttons-iconButtonDefault',(splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
|
||||
accessKey={accesskey} data-label={title || ''} {...props}>
|
||||
data-label={title || ''} {...props}>
|
||||
{icon}
|
||||
</DefaultButton>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -1366,6 +1366,7 @@ export function InputTree({hasCheckbox, treeData, onChange, ...props}){
|
|||
});
|
||||
return () => umounted = true;
|
||||
}, []);
|
||||
|
||||
return <>{isLoading ? <Loader message={gettext('Loading')}></Loader> : <PgTreeView data={finalData} hasCheckbox={hasCheckbox} selectionChange={onChange} {...props}></PgTreeView>}</>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -278,12 +278,12 @@ export function CSVToArray(strData, strDelimiter, quoteChar){
|
|||
export function hasBinariesConfiguration(pgBrowser, serverInformation) {
|
||||
const module = 'paths';
|
||||
let preference_name = 'pg_bin_dir';
|
||||
let msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences dialog.');
|
||||
let msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences.');
|
||||
|
||||
if ((serverInformation.type && serverInformation.type === 'ppas') ||
|
||||
serverInformation.server_type === 'ppas') {
|
||||
preference_name = 'ppas_bin_dir';
|
||||
msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences dialog.');
|
||||
msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences.');
|
||||
}
|
||||
|
||||
const preference = usePreferences.getState().getPreferences(module, preference_name);
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export default function SearchObjects({nodeData}) {
|
|||
|
||||
if(!rowData.show_node) {
|
||||
setErrorMsg(
|
||||
gettext('%s objects are disabled in the browser. You can enable them in the <a id="prefdlgid" class="pref-dialog-link">preferences dialog</a>.', rowData.type_label));
|
||||
gettext('%s objects are disabled in the browser. You can enable them in the <a id="prefdlgid" class="pref-dialog-link">preferences</a>.', rowData.type_label));
|
||||
|
||||
setTimeout(()=> {
|
||||
document.getElementById('prefdlgid').addEventListener('click', ()=>{
|
||||
|
|
|
|||
|
|
@ -339,18 +339,18 @@ def does_utility_exist(file):
|
|||
|
||||
if file is None:
|
||||
error_msg = gettext("Utility file not found. Please correct the Binary"
|
||||
" Path in the Preferences dialog")
|
||||
" Path in the Preferences")
|
||||
return error_msg
|
||||
|
||||
if Path(config.STORAGE_DIR) == Path(file) or \
|
||||
Path(config.STORAGE_DIR) in Path(file).parents:
|
||||
error_msg = gettext("Please correct the Binary Path in the Preferences"
|
||||
" dialog. pgAdmin storage directory can not be a"
|
||||
" utility binary directory.")
|
||||
error_msg = gettext("Please correct the Binary Path in the "
|
||||
"Preferences. pgAdmin storage directory can not "
|
||||
"be a utility binary directory.")
|
||||
|
||||
if not os.path.exists(file):
|
||||
error_msg = gettext("'%s' file not found. Please correct the Binary"
|
||||
" Path in the Preferences dialog" % file)
|
||||
" Path in the Preferences" % file)
|
||||
return error_msg
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ class _Preference():
|
|||
:param label: Display name of the options/preference
|
||||
:param _type: Type for proper validation on value
|
||||
:param default: Default value
|
||||
:param help_str: Help string to be shown in preferences dialog.
|
||||
:param help_str: Help string to be shown in preferences.
|
||||
:param min_val: minimum value
|
||||
:param max_val: maximum value
|
||||
:param options: options (Array of list objects)
|
||||
:param select2: select2 options (object)
|
||||
:param select: select options (object)
|
||||
:param fields: field schema (if preference has more than one field to
|
||||
take input from user e.g. keyboardshortcut preference)
|
||||
:param allow_blanks: Flag specify whether to allow blank value.
|
||||
|
|
@ -305,7 +305,7 @@ class Preferences():
|
|||
|
||||
:param name: Name of the module
|
||||
:param label: Display name of the module, it will be displayed in the
|
||||
preferences dialog.
|
||||
preferences.
|
||||
|
||||
:returns nothing
|
||||
"""
|
||||
|
|
@ -506,7 +506,7 @@ class Preferences():
|
|||
:param module: Name of the module
|
||||
:param category: Name of category
|
||||
:param name: Name of the option
|
||||
:param label: Label of the option, shown in the preferences dialog.
|
||||
:param label: Label of the option, shown in the preferences.
|
||||
:param _type: Type of the option.
|
||||
Allowed type of options are as below:
|
||||
boolean, integer, numeric, date, datetime,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ from selenium.webdriver.common.by import By
|
|||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from regression.feature_utils.base_feature_test import BaseFeatureTest
|
||||
from regression.feature_utils.locators import NavMenuLocators
|
||||
from regression.feature_utils.locators import NavMenuLocators, \
|
||||
PreferencesLocaltors
|
||||
|
||||
|
||||
class KeyboardShortcutFeatureTest(BaseFeatureTest):
|
||||
|
|
@ -88,32 +89,21 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
|
|||
NavMenuLocators.preference_menu_item_css)
|
||||
pref_menu_item.click()
|
||||
|
||||
self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_preference_tree_node.format('Browser'))
|
||||
wait = WebDriverWait(self.page.driver, 10)
|
||||
|
||||
display_node = self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
|
||||
'Browser', 'Display'))
|
||||
attempt = 5
|
||||
while attempt > 0:
|
||||
display_node.click()
|
||||
# After clicking the element gets loaded in to the dom but still
|
||||
# not visible, hence sleeping for a sec.
|
||||
time.sleep(1)
|
||||
if self.page.wait_for_element_to_be_visible(
|
||||
self.driver,
|
||||
NavMenuLocators.show_system_objects_pref_label_xpath, 3):
|
||||
break
|
||||
else:
|
||||
attempt -= 1
|
||||
self.page.click_tab("Preferences")
|
||||
|
||||
maximize_button = self.page.find_by_css_selector(
|
||||
NavMenuLocators.maximize_pref_dialogue_css)
|
||||
maximize_button.click()
|
||||
# Wait till the preference dialogue box is displayed by checking the
|
||||
# visibility of Show System Object label
|
||||
wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH,
|
||||
PreferencesLocaltors.show_system_objects_pref_label_xpath))
|
||||
)
|
||||
|
||||
keyboard_node = self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
|
||||
'Browser', 'Keyboard shortcuts'))
|
||||
PreferencesLocaltors.specified_preference_tree_node_xpath.format(
|
||||
'Keyboard shortcuts'))
|
||||
|
||||
keyboard_node.click()
|
||||
|
||||
for s in self.new_shortcuts:
|
||||
|
|
@ -125,7 +115,11 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
|
|||
"input".format(locator))
|
||||
|
||||
file_menu.click()
|
||||
time.sleep(1)
|
||||
file_menu.send_keys(key)
|
||||
|
||||
# save and close the preference dialog.
|
||||
self.page.click_modal('Save')
|
||||
self.page.find_by_css_selector(PreferencesLocaltors.save_btn) \
|
||||
.click()
|
||||
time.sleep(3)
|
||||
self.page.close_active_tab()
|
||||
|
|
|
|||
|
|
@ -9,16 +9,15 @@
|
|||
|
||||
import os
|
||||
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from regression.feature_utils.base_feature_test import BaseFeatureTest
|
||||
from regression.python_test_utils import test_utils
|
||||
from regression.python_test_utils import test_gui_helper
|
||||
from regression.feature_utils.locators import NavMenuLocators
|
||||
from regression.feature_utils.tree_area_locators import TreeAreaLocators
|
||||
from selenium.webdriver import ActionChains
|
||||
from regression.feature_utils.locators import NavMenuLocators, \
|
||||
PreferencesLocaltors
|
||||
import time
|
||||
|
||||
|
||||
class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
|
||||
|
|
@ -256,26 +255,18 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
|
|||
|
||||
wait = WebDriverWait(self.page.driver, 10)
|
||||
|
||||
self.page.click_tab("Preferences")
|
||||
|
||||
# Wait till the preference dialogue box is displayed by checking the
|
||||
# visibility of Show System Object label
|
||||
wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
|
||||
(By.XPATH, PreferencesLocaltors.
|
||||
show_system_objects_pref_label_xpath))
|
||||
)
|
||||
|
||||
maximize_button = self.page.find_by_css_selector(
|
||||
NavMenuLocators.maximize_pref_dialogue_css)
|
||||
maximize_button.click()
|
||||
|
||||
path = self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_preference_tree_node.format('Paths'))
|
||||
if self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_pref_node_exp_status.format('Paths')).\
|
||||
get_attribute('aria-expanded') == 'false':
|
||||
ActionChains(self.driver).double_click(path).perform()
|
||||
|
||||
binary_path = self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
|
||||
'Paths', 'Binary paths'))
|
||||
PreferencesLocaltors.specified_preference_tree_node_xpath.
|
||||
format('Binary paths'))
|
||||
binary_path.click()
|
||||
|
||||
default_binary_path = self.server['default_binary_paths']
|
||||
|
|
@ -313,10 +304,9 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
|
|||
|
||||
# save and close the preference dialog.
|
||||
if path_already_set:
|
||||
self.page.click_modal('Cancel')
|
||||
self.page.close_active_tab()
|
||||
else:
|
||||
self.page.click_modal('Save')
|
||||
|
||||
self.page.wait_for_element_to_disappear(
|
||||
lambda driver: driver.find_element(By.CSS_SELECTOR, ".ajs-modal")
|
||||
)
|
||||
self.page.find_by_css_selector(PreferencesLocaltors.save_btn) \
|
||||
.click()
|
||||
time.sleep(3)
|
||||
self.page.close_active_tab()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from regression.feature_utils.base_feature_test import BaseFeatureTest
|
|||
from regression.python_test_utils import test_utils
|
||||
from regression.feature_utils.tree_area_locators import TreeAreaLocators
|
||||
from regression.feature_utils.locators import NavMenuLocators, \
|
||||
QueryToolLocators
|
||||
QueryToolLocators, PreferencesLocaltors
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.common.by import By
|
||||
|
|
@ -30,11 +30,10 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||
test_table_name = ""
|
||||
|
||||
def before(self):
|
||||
|
||||
self._update_preferences_setting()
|
||||
self.page.add_server(self.server)
|
||||
|
||||
def runTest(self):
|
||||
self._update_preferences_setting()
|
||||
self._create_table()
|
||||
sql_query = self._get_sql_query()
|
||||
query_tool_result = self._get_query_tool_result()
|
||||
|
|
@ -97,44 +96,24 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||
NavMenuLocators.file_menu_css)
|
||||
file_menu.click()
|
||||
|
||||
self.page.retry_click(
|
||||
(By.CSS_SELECTOR, NavMenuLocators.preference_menu_item_css),
|
||||
(By.XPATH, NavMenuLocators.specified_preference_tree_node
|
||||
.format('Browser'))
|
||||
)
|
||||
pref_menu_item = self.page.find_by_css_selector(
|
||||
NavMenuLocators.preference_menu_item_css)
|
||||
pref_menu_item.click()
|
||||
|
||||
wait = WebDriverWait(self.page.driver, 10)
|
||||
|
||||
self.page.retry_click(
|
||||
(By.XPATH,
|
||||
NavMenuLocators.specified_sub_node_of_pref_tree_node.
|
||||
format('Browser', 'Display')),
|
||||
(By.XPATH,
|
||||
NavMenuLocators.show_system_objects_pref_label_xpath))
|
||||
self.page.click_tab("Preferences")
|
||||
|
||||
# Wait till the preference dialogue box is displayed by checking the
|
||||
# visibility of Show System Object label
|
||||
wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
|
||||
(By.XPATH,
|
||||
PreferencesLocaltors.show_system_objects_pref_label_xpath))
|
||||
)
|
||||
maximize_button = self.page.find_by_css_selector(
|
||||
NavMenuLocators.maximize_pref_dialogue_css)
|
||||
maximize_button.click()
|
||||
|
||||
specified_preference_tree_node_name = 'Query Tool'
|
||||
sql_editor = self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_preference_tree_node.format(
|
||||
specified_preference_tree_node_name))
|
||||
sql_editor.click()
|
||||
if self.page.find_by_xpath(
|
||||
NavMenuLocators.specified_pref_node_exp_status.
|
||||
format(specified_preference_tree_node_name)).get_attribute(
|
||||
'aria-expanded') == 'false':
|
||||
ActionChains(self.driver).double_click(sql_editor).perform()
|
||||
|
||||
option_node = self.page.find_by_xpath(
|
||||
"//*[@id='treeContainer']//div//span[text()="
|
||||
"'Results grid']//preceding::span[text()='Options'][1]")
|
||||
"//*[@id='treeContainer']//div//div[text()="
|
||||
"'Results grid']//preceding::div[text()='Options'][1]")
|
||||
# self.page.check_if_element_exists_with_scroll(option_node)
|
||||
self.page.driver.execute_script("arguments[0].scrollIntoView(false)",
|
||||
option_node)
|
||||
|
|
@ -147,4 +126,7 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||
switch_box_element.click()
|
||||
|
||||
# save and close the preference dialog.
|
||||
self.page.click_modal('Save')
|
||||
self.page.find_by_css_selector(PreferencesLocaltors.save_btn) \
|
||||
.click()
|
||||
time.sleep(3)
|
||||
self.page.close_active_tab()
|
||||
|
|
|
|||
|
|
@ -48,22 +48,6 @@ class NavMenuLocators:
|
|||
|
||||
maintenance_obj_css = "li[data-label='Maintenance...']"
|
||||
|
||||
show_system_objects_pref_label_xpath = \
|
||||
"//label[contains(text(), 'Show system objects?')]"
|
||||
|
||||
maximize_pref_dialogue_css = "button[data-label='Maximize']"
|
||||
|
||||
maximize_pref_dialogue_css = "button[data-label='Maximize']"
|
||||
|
||||
specified_pref_node_exp_status = \
|
||||
"//*[@id='treeContainer']//div//span[text()='{0}']"
|
||||
|
||||
specified_preference_tree_node = \
|
||||
"//*[@id='treeContainer']//div//span[text()='{0}']" \
|
||||
|
||||
specified_sub_node_of_pref_tree_node = \
|
||||
"//*[@id='treeContainer']//div//span[text()='{1}']"
|
||||
|
||||
insert_bracket_pair_switch_btn = \
|
||||
("//div[label[text()='Insert bracket pairs?']]/"
|
||||
"following-sibling::div//input")
|
||||
|
|
@ -112,6 +96,18 @@ class NavMenuLocators:
|
|||
".btn.btn-sm-sq.btn-primary.pg-bg-close > i"
|
||||
|
||||
|
||||
class PreferencesLocaltors:
|
||||
show_system_objects_pref_label_xpath = \
|
||||
"//label[contains(text(), 'Show system objects?')]"
|
||||
|
||||
specified_preference_tree_node_xpath = \
|
||||
("//*[@id='treeContainer']//div[contains(@class,'PgTree-nodeLabel')]"
|
||||
"[text()='{0}']")
|
||||
|
||||
save_btn = \
|
||||
"#id-preferences button[data-label='Save']"
|
||||
|
||||
|
||||
class QueryToolLocators:
|
||||
btn_save_file = "button[data-label='Save File']"
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,11 @@ class PgadminPage:
|
|||
else:
|
||||
assert False, "'Tools -> Query Tool' menu did not enable."
|
||||
|
||||
def close_active_tab(self):
|
||||
self.find_by_css_selector(f"div[data-dockid='id-main'] "
|
||||
".dock-tab.dock-tab-active "
|
||||
"button[data-label='Close']").click()
|
||||
|
||||
def close_query_tool(self, prompt=True):
|
||||
self.driver.switch_to.default_content()
|
||||
time.sleep(.5)
|
||||
|
|
|
|||
|
|
@ -103,22 +103,4 @@ describe('BgProcessManager', ()=>{
|
|||
obj.checkPending();
|
||||
expect(nSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('openProcessesPanel', ()=>{
|
||||
const panel = {};
|
||||
jest.spyOn(pgBrowser.docker.default_workspace, 'openTab').mockReturnValue(panel);
|
||||
|
||||
/* panel open */
|
||||
jest.spyOn(pgBrowser.docker.default_workspace, 'find').mockReturnValue(panel);
|
||||
jest.spyOn(pgBrowser.docker.default_workspace, 'focus');
|
||||
obj.openProcessesPanel();
|
||||
expect(pgBrowser.docker.default_workspace.focus).toHaveBeenCalled();
|
||||
expect(pgBrowser.docker.default_workspace.openTab).not.toHaveBeenCalled();
|
||||
|
||||
/* panel closed */
|
||||
jest.spyOn(pgBrowser.docker.default_workspace, 'find').mockReturnValue(null);
|
||||
obj.openProcessesPanel();
|
||||
expect(pgBrowser.docker.default_workspace.openTab).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
|
|
@ -10,8 +9,8 @@
|
|||
|
||||
|
||||
import {genericBeforeEach, getEditView} from '../genericFunctions';
|
||||
import {getBinaryPathSchema} from '../../../pgadmin/browser/server_groups/servers/static/js/binary_path.ui';
|
||||
import pgAdmin from '../fake_pgadmin';
|
||||
import { getBinaryPathSchema } from '../../../pgadmin/preferences/static/js/components/binary_path.ui';
|
||||
|
||||
describe('BinaryPathschema', ()=>{
|
||||
|
||||
|
|
|
|||
|
|
@ -1413,7 +1413,14 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.27.1, @babel/runtime@npm:^7.27.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.27.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
version: 7.27.4
|
||||
resolution: "@babel/runtime@npm:7.27.4"
|
||||
checksum: 10c0/ca99e964179c31615e1352e058cc9024df7111c829631c90eec84caba6703cc32acc81503771847c306b3c70b815609fe82dde8682936debe295b0b283b2dc6e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.27.4":
|
||||
version: 7.27.6
|
||||
resolution: "@babel/runtime@npm:7.27.6"
|
||||
checksum: 10c0/89726be83f356f511dcdb74d3ea4d873a5f0cf0017d4530cb53aa27380c01ca102d573eff8b8b77815e624b1f8c24e7f0311834ad4fb632c90a770fda00bd4c8
|
||||
|
|
@ -1446,7 +1453,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
|
||||
version: 7.27.3
|
||||
resolution: "@babel/types@npm:7.27.3"
|
||||
dependencies:
|
||||
"@babel/helper-string-parser": "npm:^7.27.1"
|
||||
"@babel/helper-validator-identifier": "npm:^7.27.1"
|
||||
checksum: 10c0/bafdfc98e722a6b91a783b6f24388f478fd775f0c0652e92220e08be2cc33e02d42088542f1953ac5e5ece2ac052172b3dadedf12bec9aae57899e92fb9a9757
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.27.6":
|
||||
version: 7.27.6
|
||||
resolution: "@babel/types@npm:7.27.6"
|
||||
dependencies:
|
||||
|
|
@ -2290,6 +2307,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@juggle/resize-observer@npm:^3.3.1":
|
||||
version: 3.4.0
|
||||
resolution: "@juggle/resize-observer@npm:3.4.0"
|
||||
checksum: 10c0/12930242357298c6f2ad5d4ec7cf631dfb344ca7c8c830ab7f64e6ac11eb1aae486901d8d880fd08fb1b257800c160a0da3aee1e7ed9adac0ccbb9b7c5d93347
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@kurkle/color@npm:^0.3.0":
|
||||
version: 0.3.4
|
||||
resolution: "@kurkle/color@npm:0.3.4"
|
||||
|
|
@ -2598,6 +2622,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nozbe/microfuzz@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@nozbe/microfuzz@npm:1.0.0"
|
||||
checksum: 10c0/16ce1b36b521f3990b83b08d2a6d1f6eb43fe240d0ebfb600e8f469187a1303c6aa576925b6c0bebee8a8df2f8e8e768e12b1c67d4ac50133468b7a02c46efa9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@npmcli/agent@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@npmcli/agent@npm:3.0.0"
|
||||
|
|
@ -13656,6 +13687,7 @@ __metadata:
|
|||
"@mui/icons-material": "npm:^7.1.1"
|
||||
"@mui/material": "npm:^7.1.0"
|
||||
"@mui/x-date-pickers": "npm:^8.5.0"
|
||||
"@nozbe/microfuzz": "npm:^1.0.0"
|
||||
"@projectstorm/react-diagrams": "npm:^7.0.4"
|
||||
"@simonwep/pickr": "npm:^1.5.1"
|
||||
"@svgr/webpack": "npm:^8.1.0"
|
||||
|
|
@ -13767,6 +13799,7 @@ __metadata:
|
|||
uplot: "npm:^1.6.32"
|
||||
uplot-react: "npm:^1.1.4"
|
||||
url-loader: "npm:^4.1.1"
|
||||
use-resize-observer: "npm:^9.1.0"
|
||||
valid-filename: "npm:^4.0.0"
|
||||
vanilla-jsoneditor: "npm:^3.3.1"
|
||||
webfonts-loader: "npm:^8.0.1"
|
||||
|
|
@ -15703,6 +15736,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-resize-observer@npm:^9.1.0":
|
||||
version: 9.1.0
|
||||
resolution: "use-resize-observer@npm:9.1.0"
|
||||
dependencies:
|
||||
"@juggle/resize-observer": "npm:^3.3.1"
|
||||
peerDependencies:
|
||||
react: 16.8.0 - 18
|
||||
react-dom: 16.8.0 - 18
|
||||
checksum: 10c0/6ccdeb09fe20566ec182b1635a22f189e13d46226b74610432590e69b31ef5d05d069badc3306ebd0d2bb608743b17981fb535763a1d7dc2c8ae462ee8e5999c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sync-external-store@npm:^1.2.0":
|
||||
version: 1.5.0
|
||||
resolution: "use-sync-external-store@npm:1.5.0"
|
||||
|
|
|
|||