diff --git a/docs/en_US/images/preferences_browser_breadcrumbs.png b/docs/en_US/images/preferences_browser_breadcrumbs.png
index 196b75a29..a0b4eea86 100644
Binary files a/docs/en_US/images/preferences_browser_breadcrumbs.png and b/docs/en_US/images/preferences_browser_breadcrumbs.png differ
diff --git a/docs/en_US/images/preferences_browser_display.png b/docs/en_US/images/preferences_browser_display.png
index 69a1d9421..b4ebcb74f 100644
Binary files a/docs/en_US/images/preferences_browser_display.png and b/docs/en_US/images/preferences_browser_display.png differ
diff --git a/docs/en_US/images/preferences_browser_keyboard_shortcuts.png b/docs/en_US/images/preferences_browser_keyboard_shortcuts.png
index ac2885cd5..586c2399e 100644
Binary files a/docs/en_US/images/preferences_browser_keyboard_shortcuts.png and b/docs/en_US/images/preferences_browser_keyboard_shortcuts.png differ
diff --git a/docs/en_US/images/preferences_browser_nodes.png b/docs/en_US/images/preferences_browser_nodes.png
index 984b31c0a..4e07a3e84 100644
Binary files a/docs/en_US/images/preferences_browser_nodes.png and b/docs/en_US/images/preferences_browser_nodes.png differ
diff --git a/docs/en_US/images/preferences_browser_processes.png b/docs/en_US/images/preferences_browser_processes.png
index 4fb388440..b7815f26c 100644
Binary files a/docs/en_US/images/preferences_browser_processes.png and b/docs/en_US/images/preferences_browser_processes.png differ
diff --git a/docs/en_US/images/preferences_browser_properties.png b/docs/en_US/images/preferences_browser_properties.png
index 0c16cb577..235db7edc 100644
Binary files a/docs/en_US/images/preferences_browser_properties.png and b/docs/en_US/images/preferences_browser_properties.png differ
diff --git a/docs/en_US/images/preferences_browser_tab_settings.png b/docs/en_US/images/preferences_browser_tab_settings.png
index b15d73e78..48005a056 100644
Binary files a/docs/en_US/images/preferences_browser_tab_settings.png and b/docs/en_US/images/preferences_browser_tab_settings.png differ
diff --git a/docs/en_US/images/preferences_dashboard_display.png b/docs/en_US/images/preferences_dashboard_display.png
index 39f31c794..6098e1735 100644
Binary files a/docs/en_US/images/preferences_dashboard_display.png and b/docs/en_US/images/preferences_dashboard_display.png differ
diff --git a/docs/en_US/images/preferences_dashboard_graphs.png b/docs/en_US/images/preferences_dashboard_graphs.png
index 4fbf34fb7..d19dbb08b 100644
Binary files a/docs/en_US/images/preferences_dashboard_graphs.png and b/docs/en_US/images/preferences_dashboard_graphs.png differ
diff --git a/docs/en_US/images/preferences_dashboard_refresh.png b/docs/en_US/images/preferences_dashboard_refresh.png
index 5a2e0b7d9..586e35b5c 100644
Binary files a/docs/en_US/images/preferences_dashboard_refresh.png and b/docs/en_US/images/preferences_dashboard_refresh.png differ
diff --git a/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png b/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png
index 10ec903df..403fcaf5d 100644
Binary files a/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png and b/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png differ
diff --git a/docs/en_US/images/preferences_erd_keyboard_shortcuts.png b/docs/en_US/images/preferences_erd_keyboard_shortcuts.png
index 3fda01a16..f1b7e0a31 100644
Binary files a/docs/en_US/images/preferences_erd_keyboard_shortcuts.png and b/docs/en_US/images/preferences_erd_keyboard_shortcuts.png differ
diff --git a/docs/en_US/images/preferences_erd_options.png b/docs/en_US/images/preferences_erd_options.png
index 6f8b6878b..f729de9ce 100644
Binary files a/docs/en_US/images/preferences_erd_options.png and b/docs/en_US/images/preferences_erd_options.png differ
diff --git a/docs/en_US/images/preferences_graph_visualiser.png b/docs/en_US/images/preferences_graph_visualiser.png
index 92436ef58..e6cecb4f3 100644
Binary files a/docs/en_US/images/preferences_graph_visualiser.png and b/docs/en_US/images/preferences_graph_visualiser.png differ
diff --git a/docs/en_US/images/preferences_header.png b/docs/en_US/images/preferences_header.png
new file mode 100644
index 000000000..5202cf57a
Binary files /dev/null and b/docs/en_US/images/preferences_header.png differ
diff --git a/docs/en_US/images/preferences_misc_file_downloads.png b/docs/en_US/images/preferences_misc_file_downloads.png
index fe8f7f981..cf0f3c560 100644
Binary files a/docs/en_US/images/preferences_misc_file_downloads.png and b/docs/en_US/images/preferences_misc_file_downloads.png differ
diff --git a/docs/en_US/images/preferences_misc_user_interface.png b/docs/en_US/images/preferences_misc_user_interface.png
index 841f9d918..9a884dbd8 100644
Binary files a/docs/en_US/images/preferences_misc_user_interface.png and b/docs/en_US/images/preferences_misc_user_interface.png differ
diff --git a/docs/en_US/images/preferences_paths_binary.png b/docs/en_US/images/preferences_paths_binary.png
index 1d07bf094..43e266d21 100644
Binary files a/docs/en_US/images/preferences_paths_binary.png and b/docs/en_US/images/preferences_paths_binary.png differ
diff --git a/docs/en_US/images/preferences_paths_help.png b/docs/en_US/images/preferences_paths_help.png
index bec54f9ac..ee21b38cf 100644
Binary files a/docs/en_US/images/preferences_paths_help.png and b/docs/en_US/images/preferences_paths_help.png differ
diff --git a/docs/en_US/images/preferences_schema_diff.png b/docs/en_US/images/preferences_schema_diff.png
index 336858494..ecdcec183 100644
Binary files a/docs/en_US/images/preferences_schema_diff.png and b/docs/en_US/images/preferences_schema_diff.png differ
diff --git a/docs/en_US/images/preferences_sql_auto_completion.png b/docs/en_US/images/preferences_sql_auto_completion.png
index e5bb3869e..a666afff2 100644
Binary files a/docs/en_US/images/preferences_sql_auto_completion.png and b/docs/en_US/images/preferences_sql_auto_completion.png differ
diff --git a/docs/en_US/images/preferences_sql_csv_output.png b/docs/en_US/images/preferences_sql_csv_output.png
index 1cd645daf..e779467cf 100644
Binary files a/docs/en_US/images/preferences_sql_csv_output.png and b/docs/en_US/images/preferences_sql_csv_output.png differ
diff --git a/docs/en_US/images/preferences_sql_display.png b/docs/en_US/images/preferences_sql_display.png
index 574e82184..caa2aa529 100644
Binary files a/docs/en_US/images/preferences_sql_display.png and b/docs/en_US/images/preferences_sql_display.png differ
diff --git a/docs/en_US/images/preferences_sql_editor.png b/docs/en_US/images/preferences_sql_editor.png
index 75e2aefe9..e417657e0 100644
Binary files a/docs/en_US/images/preferences_sql_editor.png and b/docs/en_US/images/preferences_sql_editor.png differ
diff --git a/docs/en_US/images/preferences_sql_explain.png b/docs/en_US/images/preferences_sql_explain.png
index 3709ca659..c84f3d395 100644
Binary files a/docs/en_US/images/preferences_sql_explain.png and b/docs/en_US/images/preferences_sql_explain.png differ
diff --git a/docs/en_US/images/preferences_sql_formatting.png b/docs/en_US/images/preferences_sql_formatting.png
index d121993b6..79a37eb44 100644
Binary files a/docs/en_US/images/preferences_sql_formatting.png and b/docs/en_US/images/preferences_sql_formatting.png differ
diff --git a/docs/en_US/images/preferences_sql_keyboard_shortcuts.png b/docs/en_US/images/preferences_sql_keyboard_shortcuts.png
index de61d4fa9..9f03a44f8 100644
Binary files a/docs/en_US/images/preferences_sql_keyboard_shortcuts.png and b/docs/en_US/images/preferences_sql_keyboard_shortcuts.png differ
diff --git a/docs/en_US/images/preferences_sql_options.png b/docs/en_US/images/preferences_sql_options.png
index 452b9b631..3a7f222a9 100644
Binary files a/docs/en_US/images/preferences_sql_options.png and b/docs/en_US/images/preferences_sql_options.png differ
diff --git a/docs/en_US/images/preferences_sql_results_grid.png b/docs/en_US/images/preferences_sql_results_grid.png
index 4314d7d44..af538e6fe 100644
Binary files a/docs/en_US/images/preferences_sql_results_grid.png and b/docs/en_US/images/preferences_sql_results_grid.png differ
diff --git a/docs/en_US/images/preferences_storage_options.png b/docs/en_US/images/preferences_storage_options.png
index 75aa5ddf8..c4b1dd65b 100644
Binary files a/docs/en_US/images/preferences_storage_options.png and b/docs/en_US/images/preferences_storage_options.png differ
diff --git a/docs/en_US/preferences.rst b/docs/en_US/preferences.rst
index 93138beab..b88ead5dc 100644
--- a/docs/en_US/preferences.rst
+++ b/docs/en_US/preferences.rst
@@ -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.
diff --git a/web/package.json b/web/package.json
index 8f47b5705..05c6bd48d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -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",
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js
index 44fbac399..49773638c 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js
@@ -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'},
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.ui.js
index ab820b34e..5a4c1cc24 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.ui.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.ui.js
@@ -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:[
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py
index 485faf4f2..e62f69334 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py
@@ -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
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js
index 8d12c475c..c69d88974 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js
@@ -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:[{
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui.js b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui.js
index 7521a7dfc..70cc0e8c1 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui.js
@@ -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'],
diff --git a/web/pgadmin/browser/static/js/constants.js b/web/pgadmin/browser/static/js/constants.js
index 944752552..5cd74de96 100644
--- a/web/pgadmin/browser/static/js/constants.js
+++ b/web/pgadmin/browser/static/js/constants.js
@@ -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',
diff --git a/web/pgadmin/misc/bgprocess/static/js/BgProcessManager.js b/web/pgadmin/misc/bgprocess/static/js/BgProcessManager.js
index 549921409..d891b6655 100644
--- a/web/pgadmin/misc/bgprocess/static/js/BgProcessManager.js
+++ b/web/pgadmin/misc/bgprocess/static/js/BgProcessManager.js
@@ -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) {
diff --git a/web/pgadmin/preferences/static/js/components/LeftTree.jsx b/web/pgadmin/preferences/static/js/components/LeftTree.jsx
new file mode 100644
index 000000000..dd6787b46
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/LeftTree.jsx
@@ -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 (
+
+ {
+ setSelectedItem(item.data);
+ }}
+ // don't need virtualization for preferences tree
+ overscanCount={50}
+ />
+
+ );
+}
+
+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,
+};
diff --git a/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx b/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx
index 9f05bcdbd..ffe864c4b 100644
--- a/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx
+++ b/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx
@@ -9,801 +9,410 @@
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
-import _ from 'lodash';
import url_for from 'sources/url_for';
-import React, { useEffect, useMemo } from 'react';
-import { FileType } from 'react-aspen';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Box } from '@mui/material';
import PropTypes from 'prop-types';
-import CloseIcon from '@mui/icons-material/CloseRounded';
-import HTMLReactParser from 'html-react-parser/lib/index';
-import SchemaView from '../../../../static/js/SchemaView';
import getApiInstance from '../../../../static/js/api_instance';
-import CloseSharpIcon from '@mui/icons-material/CloseSharp';
import HelpIcon from '@mui/icons-material/HelpRounded';
import SaveSharpIcon from '@mui/icons-material/SaveSharp';
-import SettingsBackupRestoreIcon from'@mui/icons-material/SettingsBackupRestore';
-import pgAdmin from 'sources/pgadmin';
-import { DefaultButton, PgIconButton, PrimaryButton } from '../../../../static/js/components/Buttons';
-import BaseUISchema from 'sources/SchemaView/base_schema.ui';
-import { getBinaryPathSchema } from '../../../../browser/server_groups/servers/static/js/binary_path.ui';
+import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
+import { PgButtonGroup, PgIconButton } from '../../../../static/js/components/Buttons';
import usePreferences from '../store';
-import { getBrowser } from '../../../../static/js/utils';
+import { usePgAdmin } from '../../../../static/js/PgAdminProvider';
+import { InputText } from '../../../../static/js/components/FormComponents';
+import { SearchRounded } from '@mui/icons-material';
+import PreferencesSchema from './preferences.ui';
+import { useFuzzySearchList } from '@nozbe/microfuzz/react';
+import Loader from 'sources/components/Loader';
-const StyledBox = styled(Box)(({theme}) => ({
- '& .PreferencesComponent-root': {
+// Import helpers from new file
+import {
+ reloadPgAdmin,
+ getNoteField,
+ prepareSubnodeData,
+ getCollectionValue,
+ showResetPrefModal
+} from './PreferencesHelper';
+import { LAYOUT_EVENTS, LayoutDockerContext } from '../../../../static/js/helpers/Layout';
+import LeftTree from './LeftTree';
+import RightPreference from './RightPreference';
+
+// --- Styled Components ---
+const Root = styled(Box)(({ theme }) => ({
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ background: theme.otherVars.emptySpaceBg,
+ overflow: 'hidden',
+
+ '& .PreferencesComponent-header': {
display: 'flex',
- flexDirection: 'column',
- flexGrow: 1,
- height: '100%',
- backgroundColor: theme.palette.background.default,
- overflow: 'hidden',
- '&$disabled': {
- color: '#ddd',
- },
- '& .PreferencesComponent-body': {
- borderColor: theme.otherVars.borderColor,
+ alignItems: 'center',
+ background: theme.palette.background.default,
+ padding: theme.spacing(1),
+ ...theme.mixins.panelBorder.bottom,
+
+ '& .PreferencesComponent-actionBtn': {
+ display: 'flex',
+ alignItems: 'center',
+ gap: theme.spacing(1),
+ },
+
+ '& .PreferencesComponent-searchInput': {
+ maxWidth: '300px',
+ marginLeft: 'auto',
+ },
+ },
+
+ '& .PreferencesComponent-body': {
+ flexGrow: 1,
+ minHeight: 0,
+ padding: theme.spacing(1),
+
+ '& .PreferencesComponent-bodyWrap': {
+ ...theme.mixins.panelBorder.all,
display: 'flex',
- flexGrow: 1,
height: '100%',
- minHeight: 0,
- overflow: 'hidden',
+ background: theme.palette.background.default,
+
'& .PreferencesComponent-treeContainer': {
- flexBasis: '25%',
- alignItems: 'flex-start',
- paddingLeft: '5px',
minHeight: 0,
flexGrow: 1,
- '& .PreferencesComponent-tree': {
- height: '100%',
- flexGrow: 1
- },
},
'& .PreferencesComponent-preferencesContainer': {
- flexBasis: '75%',
- padding: '5px',
- borderColor: theme.otherVars.borderColor + '!important',
+ borderColor: `${theme.otherVars.borderColor} !important`,
borderLeft: '1px solid',
position: 'relative',
height: '100%',
- paddingTop: '5px',
overflow: 'auto',
- '& .PreferencesComponent-preferencesContainerBackground': {
- backgroundColor: theme.palette.background.default,
- }
- },
- },
- '& .PreferencesComponent-footer': {
- borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
- padding: '0.5rem',
- display: 'flex',
- width: '100%',
- background: theme.otherVars.headerBg,
- '& .PreferencesComponent-actionBtn': {
- alignItems: 'flex-start',
- },
- '& .PreferencesComponent-buttonMargin': {
- marginLeft: '0.5em'
- },
- },
+ width: '100%',
+ '& .PreferencesComponent-noSelection': {
+ padding: theme.spacing(1),
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(0.5),
+ },
+
+ '& .PreferencesComponent-preferencesContainerBackground': {
+ backgroundColor: 'inherit',
+ },
+ },
+ },
},
- '& .Alert-footer': {
- display: 'flex',
- justifyContent: 'flex-end',
+ '& .PreferencesComponent-footer': {
+ borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
- ...theme.mixins.panelBorder.top,
- },
- '& .Alert-margin': {
- marginLeft: '0.25rem',
+ display: 'flex',
+ width: '100%',
+ background: theme.otherVars.headerBg,
+ '& .PreferencesComponent-actionBtn': {
+ alignItems: 'flex-start',
+ },
+ '& .PreferencesComponent-buttonMargin': {
+ marginLeft: '0.5em',
+ },
},
}));
-
-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;
- }
-}
-
-async function reloadPgAdmin() {
- let {name: browser} = getBrowser();
- if(browser == 'Electron') {
- await window.electronUI.log('test');
- await window.electronUI.reloadApp();
- } else {
- location.reload();
- }
-}
-
-
-function RightPanel({ schema, refreshKey, ...props }) {
- const schemaViewRef = React.useRef(null);
- let initData = () => new Promise((resolve, reject) => {
- try {
- resolve(props.initValues);
- } catch (error) {
- reject(error instanceof Error ? error : Error(gettext('Something went wrong')));
- }
- });
- useEffect(() => {
- const timeID = setTimeout(() => {
- const focusableElement = schemaViewRef.current?.querySelector(
- 'button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])'
- );
- if (focusableElement) focusableElement.focus();
- }, 50);
- return () => clearTimeout(timeID);
- }, [refreshKey]);
-
- return (
-
- {
- props.onDataChange(changedData);
- }}
- />
-
- );
-}
-
-RightPanel.propTypes = {
- schema: PropTypes.object,
- refreshKey: PropTypes.number,
- initValues: PropTypes.object,
- onDataChange: PropTypes.func
+// Helper to check if a page refresh is required
+function checkRefreshRequired(pref) {
+ // Other preferences might also require a refresh, add them here
+ return pref.name === 'user_language';
};
-
-export default function PreferencesComponent({ ...props }) {
-
- const [refreshKey, setRefreshKey] = React.useState(0);
- const [disableSave, setDisableSave] = React.useState(true);
- const prefSchema = React.useRef(new PreferencesSchema({}, []));
- const prefChangedData = React.useRef({});
- const prefTreeInit = React.useRef(false);
- const [prefTreeData, setPrefTreeData] = React.useState(null);
- const [initValues, setInitValues] = React.useState({});
- const [loadTree, setLoadTree] = React.useState(0);
+// --- Main PreferencesComponent ---
+export default function PreferencesComponent({panelId}) {
+ const [disableSave, setDisableSave] = useState(true);
+ const prefSchema = useRef(new PreferencesSchema({}, []));
+ const prefChangedData = useRef({});
+ const [prefTreeData, setPrefTreeData] = useState([]);
+ const [initValues, setInitValues] = useState({});
const api = getApiInstance();
- const firstTreeElement = React.useRef('');
+ const firstTreeElement = useRef('');
const preferencesStore = usePreferences();
+ const pgAdmin = usePgAdmin();
+ const [searchVal, setSearchVal] = useState('');
+ const [selectedItem, setSelectedItem] = useState(null);
+ const [loaderText, setLoaderText] = useState(gettext('Loading preferences...'));
+ const layoutDocker = React.useContext(LayoutDockerContext);
+ const valuesVersionRef = useRef();
- useEffect(() => {
- const pref_url = url_for('preferences.index');
- api({
- url: pref_url,
- method: 'GET',
- }).then((res) => {
- let preferencesData = [];
- let preferencesTreeData = [];
- let preferencesValues = {};
- res.data.forEach(node => {
- let id = crypto.getRandomValues(new Uint16Array(1));
- let tdata = {
- 'id': id.toString(),
- 'label': node.label,
- '_label': node.label,
- 'name': node.name,
- 'icon': '',
- 'inode': true,
- 'type': 2,
- '_type': node.label.toLowerCase(),
- '_id': id,
- '_pid': null,
- 'childrenNodes': [],
- 'expanded': true,
- 'isExpanded': true,
+ const fetchPreferences = async () => {
+ setLoaderText(gettext('Loading preferences...'));
+ try {
+ const res = await api({
+ url: url_for('preferences.index'),
+ method: 'GET',
+ });
+
+ const schemaFields = [];
+ const treeNodesData = [];
+ let values = {};
+
+ res.data.forEach((node) => {
+ const categoryNode = {
+ id: node.id.toString(),
+ name: node.label,
+ key: node.name,
+ children: [],
};
- if(firstTreeElement.current.length == 0) {
+ if (firstTreeElement.current.length === 0) {
firstTreeElement.current = node.label;
}
- node.children.forEach(subNode => {
- let sid = crypto.getRandomValues(new Uint16Array(1));
- let nodeData = {
- 'id': sid.toString(),
- 'label': subNode.label,
- '_label': subNode.label,
- 'name': subNode.name,
- 'icon': '',
- 'inode': false,
- '_type': subNode.label.toLowerCase(),
- '_id': sid,
- '_pid': node.id,
- 'type': 1,
- 'expanded': false,
+ node.children.forEach((subNode) => {
+ const nodeData = {
+ id: `${categoryNode.id}_${subNode.id}`,
+ name: subNode.label,
+ key: subNode.name,
};
- addNote(node, subNode, nodeData, preferencesData);
- setPreferences(node, subNode, nodeData, preferencesValues, preferencesData);
- tdata['childrenNodes'].push(nodeData);
+ categoryNode.children.push(nodeData);
+ schemaFields.push(...getNoteField(node, subNode, nodeData));
+
+ const {fieldItems, fieldValues} = prepareSubnodeData(node, subNode, nodeData, preferencesStore);
+ schemaFields.push(...fieldItems);
+ values = {...values, ...fieldValues};
});
-
- // set Preferences Tree data
- preferencesTreeData.push(tdata);
-
+ treeNodesData.push(categoryNode);
});
- setPrefTreeData(preferencesTreeData);
- setInitValues(preferencesValues);
- // set Preferences schema
- prefSchema.current = new PreferencesSchema(
- preferencesValues, preferencesData,
- );
- }).catch((err) => {
- pgAdmin.Browser.notifier.alert(err);
- });
- }, []);
- function setPreferences(
- node, subNode, nodeData, preferencesValues, preferencesData
- ) {
- let addBinaryPathNote = false;
- 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);
- if(addBinaryPathNote) {
- addNote(node, subNode, nodeData, preferencesData, note);
- }
- addBinaryPathNote = true;
- }
- 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 == gettext('Results grid') && node.label == gettext('Query Tool')) {
- setResultsOptions(element, subNode, preferencesValues, type);
- } else if (subNode.label == gettext('User Interface') && node.label == gettext('Miscellaneous')) {
- setWorkspaceOptions(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';
- };
+ valuesVersionRef.current = new Date().getTime();
+ setPrefTreeData(treeNodesData);
+ setInitValues(values);
+ setSelectedItem(selectedItem || treeNodesData[0]?.children[0]);
+ prefSchema.current = new PreferencesSchema(values, schemaFields);
+ setLoaderText(null);
+ } catch (err) {
+ pgAdmin.Browser.notifier.alert(err.response?.data || err.message || gettext('Failed to load preferences.'));
}
- element.type = type;
- preferencesValues[element.id] = element.value;
- }
-
- function setWorkspaceOptions(element, subNode, preferencesValues, type) {
- if (element.name== 'open_in_res_workspace') {
- let layout_control_id = null;
- subNode.preferences.forEach((_el) => {
- if(_el.name == 'layout') {
- layout_control_id = _el.id;
- }
-
- });
- element.disabled = (state) => {
- return state[layout_control_id] != 'workspace';
- };
- }
- 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;
- }
- opt.preview_src = opt.preview_src && url_for('static', { filename: opt.preview_src });
- });
- }
- }
- 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 (preferencesStore.getPreferences(node.label.toLowerCase(), element.name)?.value) {
- let temp = preferencesStore.getPreferences(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 == gettext('Nodes') && node.label == gettext('Browser')) {
- note = [gettext('This settings is to Show/Hide nodes in the object explorer.')].join('');
- } else {
- note = [note].join('');
- }
-
- if (note && note.length > 0) {
- //Add Note for Nodes
- preferencesData.push(
- {
- id: _.uniqueId('note') + subNode.id,
- type: 'note',
- text: note,
- 'parentId': nodeData['id'],
- visible: false,
- },
- );
- }
-
- }
-
- function selectChildNode(item, prefTreeInit) {
- if (item.isExpanded && item._children && item._children.length > 0 && prefTreeInit.current && event.code !== 'ArrowUp') {
- pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
- }
- }
+ };
+ // Effect to fetch preferences data on component mount
useEffect(() => {
- let firstElement = null;
- // Listen selected preferences tree node event and show the appropriate components in right panel.
- pgAdmin.Browser.Events.on('preferences:tree:selected', (event, item) => {
- if (item.type == FileType.File) {
- prefSchema.current.schemaFields.forEach((field) => {
- field.visible = field.parentId === item._metadata.data.id &&
- !field?.hidden ;
+ fetchPreferences();
+ }, []); // Added dependencies
- if(field.visible && _.isNull(firstElement)) {
- firstElement = field;
- }
-
- field.labelTooltip =
- item._parent._metadata.data.name.toLowerCase() + ':' +
- item._metadata.data.name + ':' + field.name;
- });
- prefSchema.current.categoryUpdated(item._metadata.data.id);
- setLoadTree(Date.now());
- setRefreshKey(Date.now());
- }
- else {
- selectChildNode(item, prefTreeInit);
- }
- });
-
- // Listen open preferences tree node event to default select first child node on parent node selection.
- pgAdmin.Browser.Events.on('preferences:tree:opened', (event, item) => {
- 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', addPrefTreeNode);
- }, []);
-
- function addPrefTreeNode(event, item) {
- if (item._parent._fileName == firstTreeElement.current && item._parent.isExpanded && !prefTreeInit.current) {
- pgAdmin.Browser.ptree.tree.setActiveFile(item._parent._children[0], true);
- }
- 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);
- }
- }
-
- function getControlMappedForType(type) {
- switch (type) {
- case 'text':
- return 'text';
- case 'input':
- return 'text';
- case 'boolean':
- return 'switch';
- case 'node':
- return 'switch';
- case 'integer':
- return 'numeric';
- case 'numeric':
- return 'numeric';
- case 'date':
- return 'datetimepicker';
- case 'datetime':
- return 'datetimepicker';
- case 'options':
- return 'select';
- case 'select':
- return 'select';
- case 'select2':
- return 'select';
- case 'multiline':
- return 'multiline';
- case 'switch':
- return 'switch';
- case 'keyboardshortcut':
- return 'keyboardShortcut';
- case 'radioModern':
- return 'toggle';
- case 'selectFile':
- return 'file';
- case 'threshold':
- return 'threshold';
- default:
- if (console?.warn) {
- // Warning for developer only.
- console.warn(
- 'Hmm.. We don\'t know how to render this type - \'\'' + type + '\' of control.'
- );
- }
- return 'input';
- }
- }
-
- function getCollectionValue(_metadata, value, initVals) {
- let val = value;
- if (typeof (value) == 'object') {
- if (_metadata[0].type == 'collection' && _metadata[0].schema) {
- if ('binaryPath' in value.changed[0]) {
- let pathData = [];
- let pathVersions = [];
- value.changed.forEach((chValue) => {
- pathVersions.push(chValue.version);
- });
- getPathData(initVals, pathData, _metadata, value, pathVersions);
- val = JSON.stringify(pathData);
- } else {
- let key_val = {
- 'char': value.changed[0]['key'],
- 'key_code': value.changed[0]['code'],
- };
- value.changed[0]['key'] = key_val;
- val = value.changed[0];
- }
- } else if ('warning' in value) {
- val = value['warning'] + '|' + value['alert'];
- } else if (value?.changed && value.changed.length > 0) {
- val = JSON.stringify(value.changed);
- }
- }
- return val;
- }
-
- 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, initVal);
- _data.push({
- 'category_id': _metadata[0]['cid'],
- 'id': parseInt(key),
- 'mid': _metadata[0]['mid'],
- 'name': _metadata[0]['name'],
- 'value': val,
- });
- }
- }
-
- if (_data.length > 0) {
- // Check whether layout is changed from Workspace to Classic.
- let layoutPref = _data.find(x => x.name === 'layout');
- // If layout is changed then raise the warning to close all the connections.
- if (!_.isUndefined(layoutPref) && layoutPref.value == 'classic') {
+ useEffect(()=>{
+ /* Bind the close event and check if user should be warned */
+ const deregister = layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, ()=>{
+ if(Object.keys(prefChangedData.current).length > 0) {
pgAdmin.Browser.notifier.confirm(
- gettext('Layout changed'),
- `${gettext('Switching from Workspace to Classic layout will disconnect all server connections and refresh the entire page.')}
- ${gettext('To avoid losing unsaved data, click Cancel to manually review and close your connections.')}
- ${gettext('Note that if you choose Cancel, any changes to your preferences will not be saved.')}
- ${gettext('Do you want to continue?')}`,
- function () {
- save(_data, data, true);
- },
- function () {
+ gettext('Warning'),
+ gettext('Changes will be lost. Are you sure you want to close the preferences?'),
+ function() {
+ layoutDocker.close(panelId, true);
return true;
},
- gettext('Continue'),
- gettext('Cancel')
+ null
);
- } else {
- save(_data, data);
+ return false; // Prevent closing
+ }
+ layoutDocker.close(panelId, true);
+ });
+ return ()=>{
+ deregister();
+ };
+ }, []);
+
+
+ const savePreferences = async () => {
+ const _data = [];
+ setLoaderText(gettext('Saving preferences...'));
+ for (const [key, value] of Object.entries(prefChangedData.current)) {
+ const _metadata = prefSchema.current.schemaFields.find((el) => el.id == key); // Find directly
+ if (_metadata) {
+ const val = getCollectionValue([_metadata], value, initValues); // Pass _metadata as array for consistency
+ _data.push({
+ category_id: _metadata.cid,
+ id: parseInt(key),
+ mid: _metadata.mid,
+ name: _metadata.name,
+ value: val,
+ });
}
}
- }
-
- function checkRefreshRequired(pref, requires_refresh) {
- if (pref.name == 'user_language') {
- requires_refresh = true;
+ if (_data.length === 0) {
+ // No changes to save, just close modal
+ return;
}
- return requires_refresh;
- }
+ const layoutPref = _data.find((x) => x.name === 'layout');
- function save(save_data, data, layout_changed=false) {
- api({
- url: url_for('preferences.index'),
- method: 'PUT',
- data: save_data,
- }).then(() => {
- // If layout is changed then only refresh the object explorer.
- if (layout_changed) {
- api({
- url: url_for('workspace.layout_changed'),
- method: 'DELETE',
- data: save_data,
- }).then(() => {
- pgAdmin.Browser.tree.destroy().then(
- () => {
- pgAdmin.Browser.Events.trigger(
- 'pgadmin-browser:tree:destroyed', undefined, undefined
- );
- return true;
+ const saveData = async (shouldReloadOnLayoutChange = false) => {
+ try {
+ await api({
+ url: url_for('preferences.index'),
+ method: 'PUT',
+ data: _data,
+ });
+
+ if (shouldReloadOnLayoutChange) {
+ await api({
+ url: url_for('workspace.layout_changed'),
+ method: 'DELETE', // DELETE seems unusual for layout_changed, but maintaining original logic
+ data: _data,
+ });
+ pgAdmin.Browser.tree.destroy().then(() => {
+ pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:destroyed', undefined, undefined);
+ reloadPgAdmin(); // Reload after destroying tree
+ });
+ } else {
+ const requiresTreeRefresh = _data.some((s) =>
+ ['show_system_objects', 'show_empty_coll_nodes', 'hide_shared_server', 'show_user_defined_templates'].includes(s.name) || s.name.startsWith('show_node_')
+ );
+
+ let requiresFullPageRefresh = false;
+ for (const key of Object.keys(prefChangedData.current)) {
+ const pref = preferencesStore.getPreferenceForId(Number(key));
+ if (pref && checkRefreshRequired(pref)) {
+ requiresFullPageRefresh = true;
+ break;
}
- );
- });
- } else {
- let requiresTreeRefresh = save_data.some((s)=>{
- return (
- s.name=='show_system_objects' || s.name=='show_empty_coll_nodes' ||
- s.name.startsWith('show_node_') || s.name=='hide_shared_server' ||
- s.name=='show_user_defined_templates'
- );
- });
- let requires_refresh = false;
- for (const [key] of Object.entries(data.current)) {
- let pref = preferencesStore.getPreferenceForId(Number(key));
- requires_refresh = checkRefreshRequired(pref, requires_refresh);
- }
+ }
- if (requiresTreeRefresh) {
- pgAdmin.Browser.notifier.confirm(
- gettext('Object explorer refresh required'),
- gettext(
- 'An object explorer refresh is required. Do you wish to refresh it now?'
- ),
- function () {
- pgAdmin.Browser.tree.destroy().then(
- () => {
- pgAdmin.Browser.Events.trigger(
- 'pgadmin-browser:tree:destroyed', undefined, undefined
- );
- return true;
- }
- );
- },
- function () {
- return true;
- },
- gettext('Refresh'),
- gettext('Later')
- );
- }
+ if (requiresTreeRefresh) {
+ pgAdmin.Browser.notifier.confirm(
+ gettext('Object explorer refresh required'),
+ gettext('An object explorer refresh is required. Do you wish to refresh it now?'),
+ () => {
+ pgAdmin.Browser.tree.destroy().then(() => {
+ pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:destroyed', undefined, undefined);
+ });
+ return true;
+ },
+ () => true,
+ gettext('Refresh'),
+ gettext('Later')
+ );
+ }
- if (requires_refresh) {
- pgAdmin.Browser.notifier.confirm(
- gettext('Refresh required'),
- gettext('A page refresh is required. Do you wish to refresh the page now?'),
- function () {
- /* If user clicks Yes */
- reloadPgAdmin();
- return true;
- },
- function () { props.closeModal();},
- gettext('Refresh'),
- gettext('Later')
- );
+ if (requiresFullPageRefresh) {
+ pgAdmin.Browser.notifier.confirm(
+ gettext('Refresh required'),
+ gettext('A page refresh is required. Do you wish to refresh the page now?'),
+ () => {
+ reloadPgAdmin();
+ return true;
+ },
+ () => { }, // Close modal if user opts for "Later"
+ gettext('Refresh'),
+ gettext('Later')
+ );
+ }
}
+ preferencesStore.cache(); // Refresh preferences cache
+ fetchPreferences();
+ } catch (err) {
+ pgAdmin.Browser.notifier.alert(err.response?.data || err.message || gettext('Failed to save preferences.'));
}
- // Refresh preferences cache
- preferencesStore.cache();
- props.closeModal();
- }).catch((err) => {
- pgAdmin.Browser.notifier.alert(err.response.data);
- });
- }
+ };
- const onDialogHelp = () => {
- window.open(url_for('help.static', { 'filename': 'preferences.html' }), 'pgadmin_help');
- };
-
- const reset = () => {
- const text = `${gettext('All preferences will be reset to their default values.')}
${gettext('Do you want to proceed?')}
-${gettext('Note:')}
- ${gettext('The object explorer tree will be refreshed automatically to reflect the changes.')}
-- ${gettext('If the application language changes, a reload of the application will be required. You can choose to reload later at your convenience.')}
`;
- pgAdmin.Browser.notifier.showModal(
- gettext('Reset all preferences'),
- (closeModal)=>{
- const onClick = (reset) => {
- resetPrefsToDefault(reset);
- closeModal();
- };
- return(
-
-
- {HTMLReactParser(text)}
-
-
- } onClick={()=> closeModal()}>{'Cancel'}
- } onClick={() => onClick(true)} >{gettext('Save & Reload')}
- } onClick={()=>onClick(false)}>{gettext('Save & Reload Later')}
-
-
- );
- },
- { isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: true, id: 'id-reset-preferences'},
- );
- };
-
- const resetPrefsToDefault = (refresh = false) => {
- api({
- url: url_for('preferences.index'),
- method: 'DELETE'
- }).then(()=>{
- if (refresh){
- reloadPgAdmin();
- return true;
- }
- preferencesStore.cache();
- pgAdmin.Browser.tree.destroy().then(
- () => {
- pgAdmin.Browser.Events.trigger(
- 'pgadmin-browser:tree:destroyed', undefined, undefined
- );
- return true;
- }
+ if (layoutPref && layoutPref.value === 'classic') {
+ pgAdmin.Browser.notifier.confirm(
+ gettext('Layout changed'),
+ `${gettext('Switching from Workspace to Classic layout will disconnect all server connections and refresh the entire page.')}
+ ${gettext('To avoid losing unsaved data, click Cancel to manually review and close your connections.')}
+ ${gettext('Note that if you choose Cancel, any changes to your preferences will not be saved.')}
+ ${gettext('Do you want to continue?')}`,
+ () => saveData(true), // User confirms, proceed with reload
+ () => false, // User cancels, do nothing
+ gettext('Continue'),
+ gettext('Cancel')
);
- props.closeModal();
- }).catch((err) => {
- pgAdmin.Browser.notifier.alert(err.response.data);
+ } else {
+ saveData();
+ }
+ };
+
+ const resetAllPreferences = () => {
+ showResetPrefModal(api, pgAdmin, preferencesStore, ()=>{
+ fetchPreferences();
});
};
+ const filteredList = useFuzzySearchList({
+ strategy: 'off',
+ queryText: searchVal,
+ getText: (item) => [item.label, item.helpMessage],
+ list: prefSchema.current.schemaFields,
+ mapResultItem: ({ item }) => item
+ });
+
+ const filteredItemIds = useMemo(()=>filteredList.map((item) => item.id), [filteredList]);
+
return (
-
-
-
-
-
- {
- useMemo(
- () => (prefTreeData && props.renderTree(prefTreeData)),
- [prefTreeData]
- )
- }
-
-
-
- {
- prefSchema.current && loadTree > 0 &&
- {
- Object.keys(changedData).length > 0 ?
- setDisableSave(false) : setDisableSave(true);
- prefChangedData.current = changedData;
- }}
- >
- }
-
-
-
-
+
+
+
+
} title={gettext('Help for this dialog.')}
+ icon={}
+ aria-label="Save"
+ title={gettext('Save')}
+ onClick={savePreferences}
+ disabled={disableSave || Boolean(loaderText)}
/>
-
-
- }>
- {gettext('Reset all preferences')}
-
- { props.closeModal();}}
- startIcon={
- { props.closeModal();}} />
- }>
- {gettext('Cancel')}
-
- }
- disabled={disableSave}
- onClick={() => {
- savePreferences(prefChangedData, initValues);
- }}>
- {gettext('Save')}
-
-
+
+
+ }
+ aria-label="Reset all preferences"
+ title={gettext('Reset all preferences')}
+ />
+ {
+ window.open(url_for('help.static', { filename: 'preferences.html' }), 'pgadmin_help');
+ }}
+ icon={} title={gettext('Help')}
+ />
+
+ }
+ />
+
+
+
+
+
+ {
+ prefSchema.current &&
+ {
+ setDisableSave(Object.keys(changedData).length === 0);
+ prefChangedData.current = changedData;
+ }}
+ />
+ }
+
-
+
);
}
PreferencesComponent.propTypes = {
- schema: PropTypes.array,
- initValues: PropTypes.object,
- closeModal: PropTypes.func,
- renderTree: PropTypes.func
+ panelId: PropTypes.string.isRequired,
};
diff --git a/web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx b/web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx
new file mode 100644
index 000000000..5ca81532d
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx
@@ -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.')}
${gettext('Do you want to proceed?')}
+ ${gettext('Note:')}
- ${gettext('The object explorer tree will be refreshed automatically to reflect the changes.')}
+ - ${gettext('If the application language changes, a reload of the application will be required. You can choose to reload later at your convenience.')}
`;
+
+ return (
+
+
+ {HTMLReactParser(text)}
+
+
+ } onClick={modalClose}>
+ {gettext('Cancel')}
+
+ } onClick={() => handleResetClick(true)}>
+ {gettext('Save & Reload')}
+
+ } onClick={() => handleResetClick(false)}>
+ {gettext('Save & Reload Later')}
+
+
+
+ );
+ },
+ { isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: true, id: 'id-reset-preferences' }
+ );
+}
diff --git a/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx b/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx
deleted file mode 100644
index be6e65a6f..000000000
--- a/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx
+++ /dev/null
@@ -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 ();
-}
-
-PreferencesTree.propTypes = {
- pgBrowser: PropTypes.any,
- data: PropTypes.array,
- ptree: PropTypes.any,
-};
diff --git a/web/pgadmin/preferences/static/js/components/RightPreference.jsx b/web/pgadmin/preferences/static/js/components/RightPreference.jsx
new file mode 100644
index 000000000..19f37ccf7
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/RightPreference.jsx
@@ -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 (
+
+
+ {gettext('Navigate to any below item to view or edit its preferences.')}
+ {selectedItem.children.map((child) => (
+
+ setSelectedItem(child)} underline="hover">{child.name}
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+ {
+ onDataChange(changedData);
+ }}
+ focusOnFirstInput={false}
+ />
+
+ );
+}
+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,
+};
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js b/web/pgadmin/preferences/static/js/components/binary_path.ui.js
similarity index 97%
rename from web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js
rename to web/pgadmin/preferences/static/js/components/binary_path.ui.js
index c9f361e89..b23adea97 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js
+++ b/web/pgadmin/preferences/static/js/components/binary_path.ui.js
@@ -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() {
diff --git a/web/pgadmin/preferences/static/js/components/preferences.ui.js b/web/pgadmin/preferences/static/js/components/preferences.ui.js
new file mode 100644
index 000000000..ebc59663e
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/preferences.ui.js
@@ -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;
+ }
+}
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index ad68f82cb..8176f0e63 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -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 {
- // Render preferences tree component
- return ;
- }} 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);
}
}
diff --git a/web/pgadmin/static/js/BrowserComponent.jsx b/web/pgadmin/static/js/BrowserComponent.jsx
index 63ae4cda9..336355846 100644
--- a/web/pgadmin/static/js/BrowserComponent.jsx
+++ b/web/pgadmin/static/js/BrowserComponent.jsx
@@ -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: , closable: true, group: 'playground'
};
+export const preferencesPanelData = {
+ id: BROWSER_PANELS.PREFERENCES, title: gettext('Preferences'), content: , closable: true, manualClose: true, group: 'playground'
+};
+
export const defaultTabsData = [
{
id: BROWSER_PANELS.DASHBOARD, title: gettext('Dashboard'), content: , closable: true, group: 'playground'
@@ -67,9 +72,11 @@ export const defaultTabsData = [
processesPanelData,
];
-const mainPanelGroup = {
- ...getDefaultGroup(),
- panelExtra: () =>
+const getMorePanelGroup = (tabsData) => {
+ return {
+ ...getDefaultGroup(),
+ panelExtra: () =>
+ };
};
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: () => } : {...getDefaultGroup()},
+ 'playground': item?.tabsData ? getMorePanelGroup(item?.tabsData) : {...getDefaultGroup()},
}}
resetToTabPanel={BROWSER_PANELS.MAIN}
isLayoutVisible={currentWorkspace == item.workspace}
diff --git a/web/pgadmin/static/js/PgTreeView/index.jsx b/web/pgadmin/static/js/PgTreeView/index.jsx
index c79ddad59..3b3084284 100644
--- a/web/pgadmin/static/js/PgTreeView/index.jsx
+++ b/web/pgadmin/static/js/PgTreeView/index.jsx
@@ -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 (
+ return (
{treeData.length > 0 ?
- treeContainerRef.current = containerRef} className={'PgTree-tree'}>
-
- {({ width, height }) => (
- {
- treeObj.current = obj;
- }}
- width={isNaN(width) ? 100 : width}
- height={isNaN(height) ? 100 : height}
- data={treeData}
- disableDrag={true}
- disableDrop={true}
- dndRootElement={treeContainerRef.current}
- {...props}
- >
- {
- (props) =>
- }
-
- )}
-
-
+ {
+ 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) =>
+ }
+
:
@@ -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 (
-
-
- {
- hasCheckbox ?
:
}
- onChange={onCheckboxSelection} /> :
-
- }
-
{node.data.name}
+
+
+
+
+
+
+ {node.data.name}
+
+
);
}
@@ -197,7 +219,31 @@ DefaultNode.propTypes = {
onNodeSelectionChange: PropTypes.func
};
-function CollectionArrow({ node, tree, selectedNodeIds }) {
+function IndentIcon({node, hasCheckbox, isSelected, isIndeterminate, onCheckboxSelection}) {
+ if(hasCheckbox) {
+ return (
+
:
}
+ onChange={onCheckboxSelection}
+ />
+ );
+ }
+ if(hasExpand(node)) {
+ return <>>;
+ }
+ return
;
+}
+
+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 (
-
{/* handled by parent */ }}>
- {node.isInternal && node?.children.length > 0 ? : null}
-
- );
+ if(hasExpand(node)) {
+ return (
+
{/* handled by parent */ }}>
+ {node.isOpen ? : }
+
+ );
+ }
+ return (
);
}
-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) {
diff --git a/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx b/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
index cb7468c44..b17bdd067 100644
--- a/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
+++ b/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
@@ -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}
/>
{showFooter &&
@@ -268,4 +269,5 @@ SchemaDialogView.propTypes = {
Notifier: PropTypes.object,
checkDirtyOnEnableSave: PropTypes.bool,
customCloseBtnName: PropTypes.string,
+ focusOnFirstInput: PropTypes.bool,
};
diff --git a/web/pgadmin/static/js/components/Buttons.jsx b/web/pgadmin/static/js/components/Buttons.jsx
index 972acacb7..f30384254 100644
--- a/web/pgadmin/static/js/components/Buttons.jsx
+++ b/web/pgadmin/static/js/components/Buttons.jsx
@@ -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 =
;
+ if(shortcut) {
+ shortcutTitle = ;
}
+ 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 (
-
+
+ data-label={title || ''} {...props}>
{icon}
@@ -195,11 +212,11 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
);
} else {
return (
-
+
+ data-label={title || ''} {...props}>
{icon}
@@ -208,10 +225,10 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
}
} else if(color == 'primary') {
return (
-
+
+ data-label={title || ''} {...props}>
{icon}
@@ -219,10 +236,10 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
);
} else {
return (
-
+
+ data-label={title || ''} {...props}>
{icon}
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index ea517f1f1..a40ccbe52 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -1366,6 +1366,7 @@ export function InputTree({hasCheckbox, treeData, onChange, ...props}){
});
return () => umounted = true;
}, []);
+
return <>{isLoading ? : }>;
}
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 40cfcfa66..0b8b99ab7 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -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);
diff --git a/web/pgadmin/tools/search_objects/static/js/SearchObjects.jsx b/web/pgadmin/tools/search_objects/static/js/SearchObjects.jsx
index 8e007d5af..ae5713f38 100644
--- a/web/pgadmin/tools/search_objects/static/js/SearchObjects.jsx
+++ b/web/pgadmin/tools/search_objects/static/js/SearchObjects.jsx
@@ -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 preferences dialog.', rowData.type_label));
+ gettext('%s objects are disabled in the browser. You can enable them in the preferences.', rowData.type_label));
setTimeout(()=> {
document.getElementById('prefdlgid').addEventListener('click', ()=>{
diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py
index 647191268..019403532 100644
--- a/web/pgadmin/utils/__init__.py
+++ b/web/pgadmin/utils/__init__.py
@@ -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
diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py
index c26c71cb0..8d9077d73 100644
--- a/web/pgadmin/utils/preferences.py
+++ b/web/pgadmin/utils/preferences.py
@@ -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,
diff --git a/web/regression/feature_tests/keyboard_shortcut_test.py b/web/regression/feature_tests/keyboard_shortcut_test.py
index 9c7cd23f5..9d11d0b6d 100644
--- a/web/regression/feature_tests/keyboard_shortcut_test.py
+++ b/web/regression/feature_tests/keyboard_shortcut_test.py
@@ -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()
diff --git a/web/regression/feature_tests/pg_utilities_backup_restore_test.py b/web/regression/feature_tests/pg_utilities_backup_restore_test.py
index c438040ab..b475ab526 100644
--- a/web/regression/feature_tests/pg_utilities_backup_restore_test.py
+++ b/web/regression/feature_tests/pg_utilities_backup_restore_test.py
@@ -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()
diff --git a/web/regression/feature_tests/test_copy_sql_to_query_tool.py b/web/regression/feature_tests/test_copy_sql_to_query_tool.py
index c40a3387d..ff0798068 100644
--- a/web/regression/feature_tests/test_copy_sql_to_query_tool.py
+++ b/web/regression/feature_tests/test_copy_sql_to_query_tool.py
@@ -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()
diff --git a/web/regression/feature_utils/locators.py b/web/regression/feature_utils/locators.py
index aa861381e..503fdb07c 100644
--- a/web/regression/feature_utils/locators.py
+++ b/web/regression/feature_utils/locators.py
@@ -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']"
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index 69e9e6757..3e0c442e0 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -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)
diff --git a/web/regression/javascript/processes/BgProcessManager.spec.js b/web/regression/javascript/processes/BgProcessManager.spec.js
index 845eb714b..fe71ff08a 100644
--- a/web/regression/javascript/processes/BgProcessManager.spec.js
+++ b/web/regression/javascript/processes/BgProcessManager.spec.js
@@ -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();
- });
});
diff --git a/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js
index 6904e1c35..f304ab0d8 100644
--- a/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js
@@ -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', ()=>{
diff --git a/web/yarn.lock b/web/yarn.lock
index 6f3e572f7..a9bea0e4f 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -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"