Added native menu support in desktop mode. #5503

This commit is contained in:
Nikhil Mohite
2022-12-06 18:16:36 +05:30
committed by GitHub
parent cddb24bc50
commit 2480d08e0f
78 changed files with 1126 additions and 523 deletions

View File

@@ -7,11 +7,16 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { generateNodeUrl } from './node_ajax';
import { getBrowser } from '../../../static/js/utils';
import createMainMenus, {refreshMainMenuItems, MainMenuItemFactory} from './main_menu';
import { getContextMenu} from './new_menu';
import _ from 'lodash';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
import { checkMasterPassword } from '../../../static/js/Dialogs/index';
import { pgHandleItemError } from '../../../static/js/utils';
import { Search } from './quick_search/trigger_search';
define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery',
@@ -304,6 +309,8 @@ define('pgadmin.browser', [
// Help menus
help: {},
},
native_context_menus: {},
add_panels: function() {
/* Add hooked-in panels by extensions */
let panels = JSON.parse(pgBrowser.panels_items);
@@ -366,6 +373,77 @@ define('pgadmin.browser', [
scripts[n].push({'name': m, 'path': p, loaded: false});
},
masterpass_callback_queue: [],
getMenuList: function(name, item, d, skipDisabled=false) {
let obj = this;
let category = {
'common': []
};
for(let _key of Object.keys(obj.menus[name][d._type])){
let menuCategory = obj.menus[name][d._type][_key].category;
let menuItem = obj.menus[name][d._type][_key];
if(menuCategory in this.menu_categories) {
category[menuCategory] ? category[menuCategory].push(menuItem): category[menuCategory] = [menuItem];
} else if (!_.isUndefined(menuCategory)) {
category[menuCategory] ? category[menuCategory].push(menuItem): category[menuCategory] = [menuItem];
} else {
category['common'].push(menuItem);
}
}
let menuItemList = [];
for(let c in category) {
if((c in obj.menu_categories || category[c].length > 1) && c != 'common' ) {
let is_all_option_dis = true;
category[c].forEach((c)=> {
c.is_disabled = c.disabled(d, item);
if(is_all_option_dis)
is_all_option_dis = c.is_disabled;
});
let label = c in obj.menu_categories ? obj.menu_categories[c].label : c;
let priority = c in obj.menu_categories ? obj.menu_categories[c].priority || 10 : 10;
if(!is_all_option_dis && skipDisabled){
let _menuItem = MainMenuItemFactory.create({
name: c,
label: label,
module:c,
category: c,
menu_items: category[c],
priority: priority
});
menuItemList.push(_menuItem);
} else if(!skipDisabled){
let _menuItem = MainMenuItemFactory.create({
name: c,
label: label,
module:c,
category: c,
menu_items: category[c],
priority: priority
});
menuItemList.push(_menuItem);
}
} else {
category[c].forEach((c)=> {
c.is_disabled = c.disabled(d, item);
});
category[c].forEach((m)=> {
if(!skipDisabled) {
menuItemList.push(m);
} else if(skipDisabled && !m.is_disabled){
menuItemList.push(m);
}
});
}
}
return menuItemList;
},
// Enable/disable menu options
enable_disable_menus: function(item) {
// Mechanism to enable/disable menus depending on the condition.
@@ -376,12 +454,13 @@ define('pgadmin.browser', [
$obj_mnu = navbar.find('li#mnu_obj .dropdown-menu').first(),
// data for current selected object
d = item ? obj.tree.itemData(item) : undefined,
update_menuitem = function(m) {
update_menuitem = function(m, o) {
if (m instanceof pgAdmin.Browser.MenuItem) {
m.update(d, item);
} else {
}
else {
for (let key in m) {
update_menuitem(m[key]);
update_menuitem(m[key], o);
}
}
};
@@ -396,19 +475,38 @@ define('pgadmin.browser', [
// All menus (except for the object menus) are already present.
// They will just require to check, wheather they are
// enabled/disabled.
_.each([
{m: 'file', id: '#mnu_file'},
{m: 'management', id: '#mnu_management'},
{m: 'tools', id: '#mnu_tools'},
{m: 'help', id:'#mnu_help'}], function(o) {
_.each( obj.menus[o.m], function(m) { update_menuitem(m); });
});
let {name: browser} = getBrowser();
if(browser == 'Nwjs') {
pgBrowser.MainMenus.forEach((menu) => {
menu.menuItems.forEach((item) => {
item.setDisabled(item.disabled(d, item));
});
});
}else {
_.each([
{name: 'file', id: '#mnu_file', label: gettext('File')},
{name: 'management', id: '#mnu_management', label: gettext('Management')},
{name: 'tools', id: '#mnu_tools', label: gettext('Tools')},
{name: 'help', id:'#mnu_help', label: gettext('Help')}], function(o) {
_.each( obj.menus[o.name], function(m) {
update_menuitem(m, o);
});
});
}
// Create the object menu dynamically
if (item && obj.menus['object'] && obj.menus['object'][d._type]) {
pgAdmin.Browser.MenuCreator(
obj.Nodes, $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item
);
if(browser == 'Nwjs') {
let menuItemList = obj.getMenuList('object', item, d);
let objectMenu = pgBrowser.MainMenus.filter((menu) => menu.name == 'object');
objectMenu.length > 0 && refreshMainMenuItems(objectMenu[0], menuItemList);
let ctxMenuList = obj.getMenuList('context', item, d, true);
obj.native_context_menus = getContextMenu(ctxMenuList, item, d);
} else {
pgAdmin.Browser.MenuCreator(
obj.Nodes, $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item
);
}
} else {
// Create a dummy 'no object seleted' menu
let create_submenu = pgAdmin.Browser.MenuGroup(
@@ -465,7 +563,6 @@ define('pgadmin.browser', [
initializeModalProvider();
initializeNotifier();
// Syntax highlight the SQL Pane
if(document.getElementById('sql-textarea')){
obj.editor = CodeMirror.fromTextArea(
@@ -517,9 +614,14 @@ define('pgadmin.browser', [
context_menu = {};
if(item) obj.tree.select(item);
pgAdmin.Browser.MenuCreator(
obj.Nodes, $div, menus, obj.menu_categories, d, item, context_menu
);
let {name: browser} = getBrowser();
if(browser == 'Nwjs'){
context_menu = obj.native_context_menus;
} else {
pgAdmin.Browser.MenuCreator(
obj.Nodes, $div, menus, obj.menu_categories, d, item, context_menu
);
}
return {
autoHide: false,
@@ -677,12 +779,15 @@ define('pgadmin.browser', [
let self = this,
pgMenu = this.menus,
MenuItem = pgAdmin.Browser.MenuItem;
let {name: browser} = getBrowser();
// MenuItem = pgAdmin.Browser.MenuItem;
_.each(menus, function(m) {
_.each(m.applies, function(a) {
/* We do support menu type only from this list */
if ($.inArray(a, [
'context', 'file', 'edit', 'object',
'management', 'tools', 'help']) >= 0) {
// if ($.inArray(a, [
// 'context', 'file', 'edit', 'object',
// 'management', 'tools', 'help']) >= 0) {
if(['context', 'file', 'edit', 'object','management', 'tools', 'help'].indexOf(a) > -1){
let _menus;
// If current node is not visible in browser tree
@@ -714,14 +819,55 @@ define('pgadmin.browser', [
} else if(_.isString(_m.enable) && _m.enable.toLowerCase() == 'false') {
enable = false;
}
return new MenuItem({
name: _m.name, label: _m.label, module: _m.module,
category: _m.category, callback: _m.callback,
priority: _m.priority, data: _m.data, url: _m.url || '#',
target: _m.target, icon: _m.icon,
enable,
node: _m.node, checked: _m.checked, below: _m.below,
});
// This is to handel quick search callback
if(gettext(_m.label) == gettext('Quick Search')) {
_m.callback = () => {
// Render Search component
Notify.showModal(gettext('Quick Search'), (closeModal) => {
return <Search closeModal={closeModal}/>;
},
{ isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: false}
);
};
}
if(browser == 'Nwjs') {
return MainMenuItemFactory.create({
name: _m.name,
label: _m.label,
module: _m.module,
category: _m.category,
callback: typeof _m.module == 'object' && _m.module[_m.callback] && _m.callback in _m.module[_m.callback] ? _m.module[_m.callback] : _m.callback,
priority: _m.priority,
data: _m.data,
url: _m.url || '#',
target: _m.target,
icon: _m.icon,
enable: enable ? enable : true,
node: _m.node,
checked: _m.checked,
below: _m.below,
applies: _m.applies,
});
} else {
return new MenuItem({
name: _m.name,
label: _m.label,
module: _m.module,
category: _m.category,
callback: _m.callback,
priority: _m.priority,
data: _m.data,
url: _m.url || '#',
target: _m.target,
icon: _m.icon,
enable,
node: _m.node,
checked: _m.checked,
below: _m.below,
});
}
};
if (!_.has(_menus, m.name)) {
@@ -747,35 +893,41 @@ define('pgadmin.browser', [
});
},
// Create the menus
create_menus: function() {
create_menus: function () {
let { name: browser } = getBrowser();
// Add Native menus if NWjs app
if (browser == 'Nwjs') {
createMainMenus();
this.enable_disable_menus();
} else {
/* Create menus */
let navbar = $('#navbar-menu > ul').first();
let obj = this;
/* Create menus */
let navbar = $('#navbar-menu > ul').first();
let obj = this;
_.each([
{ menu: 'file', id: '#mnu_file' },
{ menu: 'management', id: '#mnu_management' },
{ menu: 'tools', id: '#mnu_tools' },
{ menu: 'help', id: '#mnu_help' }],
function (o) {
let $mnu = navbar.children(o.id).first(),
$dropdown = $mnu.children('.dropdown-menu').first();
$dropdown.empty();
if (o.menu == 'help') {
$dropdown.append('<div id="quick-search-component"></div>');
$dropdown.append('<div class="menu-groups"><span class="fa fa-list" style="font-weight:900 !important;"></span> &nbsp;' + gettext('SUGGESTED SITES') + '</div>');
}
_.each([
{menu: 'file', id: '#mnu_file'},
{menu: 'management', id: '#mnu_management'},
{menu: 'tools', id: '#mnu_tools'},
{menu: 'help', id:'#mnu_help'}],
function(o) {
let $mnu = navbar.children(o.id).first(),
$dropdown = $mnu.children('.dropdown-menu').first();
$dropdown.empty();
if(o.menu == 'help'){
$dropdown.append('<div id="quick-search-component"></div>');
$dropdown.append('<div class="menu-groups"><span class="fa fa-list" style="font-weight:900 !important;"></span> &nbsp;' + gettext('SUGGESTED SITES') + '</div>');
}
if (pgAdmin.Browser.MenuCreator(
obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories
)) {
$mnu.removeClass('d-none');
}
});
if (pgAdmin.Browser.MenuCreator(
obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories
)) {
$mnu.removeClass('d-none');
}
});
navbar.children('#mnu_obj').removeClass('d-none');
obj.enable_disable_menus();
navbar.children('#mnu_obj').removeClass('d-none');
obj.enable_disable_menus();
}
},
// General function to handle callbacks for object or dialog help.
showHelp: function(type, url, node, item) {

View File

@@ -45,7 +45,6 @@ define([
name: 'refresh', node: this.type, module: this,
applies: ['object', 'context'], callback: 'refresh',
priority: 2, label: gettext('Refresh'),
icon: 'fa fa-sync-alt',
}]);
// show query tool only in context menu of supported nodes.
@@ -55,7 +54,6 @@ define([
name: 'show_query_tool', node: this.type, module: this,
applies: ['context'], callback: 'show_query_tool',
priority: 998, label: gettext('Query Tool'),
icon: 'pg-font-icon icon-query_tool',
}]);
// show search objects same as query tool
@@ -63,7 +61,6 @@ define([
name: 'search_objects', node: this.type, module: this,
applies: ['context'], callback: 'show_search_objects',
priority: 997, label: gettext('Search Objects...'),
icon: 'fa fa-search',
}]);
// show psql tool same as query tool.
@@ -72,7 +69,6 @@ define([
name: 'show_psql_tool', node: this.type, module: this,
applies: ['context'], callback: 'show_psql_tool',
priority: 998, label: gettext('PSQL Tool'),
icon: 'fas fa-terminal',
}]);
}
}

View File

@@ -0,0 +1,74 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import pgAdmin from 'sources/pgadmin';
import { getBrowser } from '../../../static/js/utils';
import Menu, { MenuItem } from './new_menu';
export let MainMenus = [
{ label: gettext('File'), name: 'file', id: 'mnu_file', index: 0, addSepratior: true },
{ label: gettext('Object'), name: 'object', id: 'mnu_obj', index: 1, addSepratior: true },
{ label: gettext('Tools'), name: 'tools', id: 'mnu_tools', index: 2, addSepratior: true },
{ label: gettext('Help'), name: 'help', id: 'mnu_help', index: 5, addSepratior: false }
];
let {name: browser} = getBrowser();
export default function createMainMenus() {
pgAdmin.Browser.MainMenus = [];
MainMenus.forEach((_menu) => {
let menuObj = Menu.create(_menu.name, _menu.label, _menu.id, _menu.index, _menu.addSepratior);
pgAdmin.Browser.MainMenus.push(menuObj);
// Don't add menuItems for Object menu as it's menuItems get changed on tree selection.
if(_menu.name !== 'object') {
menuObj.addMenuItems(Object.values(pgAdmin.Browser.menus[_menu.name]));
let priority = null;
menuObj.menuItems.forEach((menuItem, index)=> {
if(index == 0) {
priority = menuItem.priority;
}
if(priority !== menuItem.priority) {
let separateMenuItem = new MenuItem({type: 'separator'});
menuObj.addMenuItem(separateMenuItem, index);
}
});
}
});
}
export function refreshMainMenuItems(menu, menuItems) {
if(browser == 'Nwjs') {
menu.setMenuItems(menuItems);
pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', menu);
}
}
// Factory to create menu items for main menu.
export class MainMenuItemFactory {
static create(options) {
return new MenuItem({...options, callback: () => {
// Some callbacks registered in 'callbacks' check and call specifiec callback function
if (options.module && 'callbacks' in options.module && options.module.callbacks[options.callback]) {
options.module.callbacks[options.callback].apply(options.module, [options.data, pgAdmin.Browser.tree.selected()]);
} else if (options.module && options.module[options.callback]) {
options.module[options.callback].apply(options.module, [options.data, pgAdmin.Browser.tree.selected()]);
} else if (options?.callback) {
options.callback(options);
} else {
if (options.url != '#') {
window.open(options.url);
}
}
}}, (menu, item)=> {
pgAdmin.Browser.Events.trigger('pgadmin:nw-enable-disable-menu-items', menu, item);
});
}
}

View File

@@ -7,6 +7,7 @@
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import {MenuItem as NewMenuItem} from './new_menu';
define([
'sources/pgadmin', 'jquery', 'sources/utils', 'sources/gettext',
@@ -334,7 +335,17 @@ define([
let group = groups[m.category || 'common'] =
groups[m.category || 'common'] || [];
group.push(m);
} else {
} else if(m instanceof NewMenuItem) {
if (m.$el) {
m.$el.remove();
delete m.$el;
}
m.generate(this, self, this.context_menu_callback, item);
let group = groups[m.category || 'common'] =
groups[m.category || 'common'] || [];
group.push(m);
}
else {
for (let key in m) {
update_menuitem(m[key]);
}
@@ -431,6 +442,7 @@ define([
let d = this.$element.data('pgMenu');
if (d.cb) {
let cb = d.module && d.module['callbacks'] && d.module['callbacks'][d.cb] || d.module && d.module[d.cb];
cb = cb || d.cb;
if (cb) {
cb.apply(d.module, [d.data, pgAdmin.Browser.tree.selected()]);
ev.preventDefault();

View File

@@ -0,0 +1,223 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import gettext from 'sources/gettext';
export default class Menu {
constructor(name, label, id, index, addSepratior) {
this.label = label;
this.name = name;
this.id = id;
this.index = index || 1;
this.menuItems = [],
this.addSepratior = addSepratior || false;
}
static create(name, label, id, index, addSepratior) {
let menuObj = new Menu(name, label, id, index, addSepratior);
return menuObj;
}
addMenuItem(menuItem, index=null) {
if (menuItem instanceof MenuItem) {
menuItem.parentMenu = this;
if(index) {
this.menuItems.splice(index, 0, menuItem);
} else {
this.menuItems.push(menuItem);
// Sort by alphanumeric ordered first
this.menuItems.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
this.menuItems.sort(function (a, b) {
return a.priority - b.priority;
});
}
} else {
throw new Error(gettext('Invalid MenuItem instance'));
}
}
addMenuItems(menuItems) {
menuItems.forEach((item) => {
if (item instanceof MenuItem) {
item.parentMenu = this;
this.menuItems.push(item);
} else {
let subItems = Object.values(item);
subItems.forEach((subItem)=> {
if (subItem instanceof MenuItem) {
subItem.parentMenu = this;
this.menuItems.push(subItem);
} else {
throw new Error(gettext('Invalid MenuItem instance'));
}
});
}
});
// Sort by alphanumeric ordered first
this.menuItems.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
this.menuItems.sort(function (a, b) {
return a.priority - b.priority;
});
}
setMenuItems(menuItems) {
this.menuItems = menuItems;
// Sort by alphanumeric ordered first
this.menuItems.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
this.menuItems.sort(function (a, b) {
return a.priority - b.priority;
});
}
getMenuItems() {
return this.menuItems;
}
static getContextMenus(menuList, item, node) {
// Sort by alphanumeric ordered first
menuList.sort(function (a, b) {
return a.label.localeCompare(b.label);
});
// Sort by priority
menuList.sort(function (a, b) {
return a.priority - b.priority;
});
let ctxMenus = {};
let ctxIndex = 1;
menuList.forEach(ctx => {
let ctx_uid = _.uniqueId('ctx_');
let sub_ctx_item = {};
ctx.is_disabled = ctx.disabled(node, item);
if ('menu_items' in ctx && ctx.menu_items) {
ctx.menu_items.forEach((c) => {
c.is_disabled = c.disabled(node, item);
if (!c.is_disabled) {
sub_ctx_item[ctx_uid + _.uniqueId('_sub_')] = c.getContextItem(c.label, c.is_disabled);
}
});
}
if (!ctx.is_disabled) {
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_itm'] = ctx.getContextItem(ctx.label, ctx.is_disabled, sub_ctx_item);
if (_.size(sub_ctx_item) > 0 && ['register', 'create'].includes(ctx.category)) {
ctxMenus[ctx_uid + '_' + ctx.priority + '_' + + ctxIndex + '_sep'] = '----';
}
}
ctxIndex++;
});
return ctxMenus;
}
}
export class MenuItem {
constructor(options, onDisableChange) {
let menu_opts = [
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
'category', 'target', 'url', 'node',
'checked', 'below', 'menu_items', 'is_checkbox', 'action', 'applies', 'is_native_only', 'type'
];
let defaults = {
url: '#',
target: '_self',
enable: true,
type: 'normal'
};
_.extend(this, defaults, _.pick(options, menu_opts));
if (!this.callback) {
this.callback = (item) => {
if (item.url != '#') {
window.open(item.url);
}
};
}
this.onDisableChange = onDisableChange;
}
static create(options) {
return MenuItem(options);
}
contextMenuCallback(self) {
self.callback();
}
getContextItem(label, is_disabled, sub_ctx_item) {
let self = this;
return {
name: label,
disabled: is_disabled,
callback: () => { this.contextMenuCallback(self); },
...(sub_ctx_item && Object.keys(sub_ctx_item).length > 0) && { items: sub_ctx_item }
};
}
setDisabled(disabled) {
this.is_disabled = disabled;
this.onDisableChange?.(this.parentMenu, this);
}
/*
* Checks this menu enable/disable state based on the selection.
*/
disabled(node, item) {
if (this.enable == undefined) {
return false;
}
if (this.node) {
if (!node) {
return true;
}
if (_.isArray(this.node) ? (
_.indexOf(this.node, node) == -1
) : (this.node != node._type)) {
return true;
}
}
if (_.isBoolean(this.enable)) return !this.enable;
if (_.isFunction(this.enable)) {
return !this.enable.apply(this.module, [node, item, this.data]);
}
if (this.module && _.isBoolean(this.module[this.enable])) {
return !this.module[this.enable];
}
if (this.module && _.isFunction(this.module[this.enable])) {
return !(this.module[this.enable]).apply(this.module, [node, item, this.data]);
}
return false;
}
}
export function getContextMenu(menu, item, node) {
return Menu.getContextMenus(menu, item, node);
}

View File

@@ -111,7 +111,7 @@ define('pgadmin.browser.node', [
callback: 'refresh',
priority: 2,
label: gettext('Refresh...'),
icon: 'fa fa-sync-alt',
enable: true,
}]);
if (self.canEdit) {
@@ -126,7 +126,6 @@ define('pgadmin.browser.node', [
data: {
'action': 'edit',
},
icon: 'fa fa-edit',
enable: _.isFunction(self.canEdit) ?
function() {
return !!(self.canEdit.apply(self, arguments));
@@ -147,7 +146,6 @@ define('pgadmin.browser.node', [
'url': 'drop',
data_disabled: gettext('The selected tree node does not support this option.'),
},
icon: 'fa fa-trash-alt',
enable: _.isFunction(self.canDrop) ?
function() {
return !!(self.canDrop.apply(self, arguments));
@@ -166,7 +164,6 @@ define('pgadmin.browser.node', [
data: {
'url': 'delete',
},
icon: 'fa fa-trash-alt',
enable: _.isFunction(self.canDropCascade) ?
function() {
return self.canDropCascade.apply(self, arguments);
@@ -193,7 +190,6 @@ define('pgadmin.browser.node', [
callback: 'show_query_tool',
priority: 998,
label: gettext('Query Tool'),
icon: 'pg-font-icon icon-query_tool',
enable: enable,
}]);
@@ -211,7 +207,6 @@ define('pgadmin.browser.node', [
name: 'show_psql_tool', node: this.type, module: this,
applies: ['context'], callback: 'show_psql_tool',
priority: 998, label: gettext('PSQL Tool'),
icon: 'fas fa-terminal',
}]);
}
}
@@ -240,7 +235,6 @@ define('pgadmin.browser.node', [
'script': stype,
data_disabled: gettext('The selected tree node does not support this option.'),
},
icon: 'fa fa-pencil-alt',
enable: self.check_user_permission,
}]);
});

View File

@@ -1,29 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import {Search} from './quick_search/trigger_search';
// TODO: GUI, Add the logic to show loading screen while fetching result
const onResultFetch = (url, data) => {
// URL can be used for displaying all the result in new page
// data will be array of search <name> -> <link>
console.warn('URL = ' + url);
console.warn(data);
};
setTimeout(function(){
if (document.getElementById('quick-search-component')) {
ReactDOM.render(
<Search onResult={onResultFetch} />,
document.getElementById('quick-search-component')
);
}
},500);

View File

@@ -6,86 +6,79 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import {MenuItem as NewMenuItem} from '../new_menu';
import { MainMenus } from '../main_menu';
import pgAdmin from 'sources/pgadmin';
import { getBrowser } from '../../../../static/js/utils';
// Allow us to
const getMenuName = (item) => {
let aLinks = item.getElementsByTagName('a');
let name;
if (aLinks.length > 0) {
name = (aLinks[0].text).trim();
name = name.replace(/\.+$/g, '');
}
return name;
return item.label;
};
export function menuSearch(param, props) {
let LAST_MENU;
param = param.trim();
const setState = props.setState;
let result = [];
if (window.pgAdmin.Browser.utils.app_name) {
LAST_MENU = gettext('About '+ window.pgAdmin.Browser.utils.app_name);
}
// Here we will add the matches
const parseLI = (_menu, path) => {
let _name = getMenuName(_menu);
if (_name && _name.toLowerCase().indexOf(param.toLowerCase()) != -1) {
let _res = {};
_res[_name] = path;
_res['element'] = _menu.children[0];
result.push(_res);
}
// Check if last menu then update the parent component's state
if (_name === LAST_MENU) {
setState(state => ({
...state,
fetched: true,
data: result,
}));
}
};
// Recursive function to search in UL
const parseUL = (menu, path) => {
const menus = Array.from(menu.children);
menus.forEach((_menu) => {
let _name, _path;
if (_menu.tagName == 'UL') {
_name = getMenuName(_menu);
_path = `${path}/${_name}`;
iterItem(_menu, _path);
} else if (_menu.tagName == 'LI') {
if (_menu.classList.contains('dropdown-submenu')) {
_name = getMenuName(_menu);
_path = `${path}/${_name}`;
iterItem(_menu, _path);
const iterItem = (subMenus, path, parentPath) => {
subMenus.forEach((subMenu) =>{
if(subMenu instanceof NewMenuItem || subMenu instanceof pgAdmin.Browser.MenuItem) {
if(subMenu.type != 'separator' && subMenu?.label?.toLowerCase().indexOf(param.toLowerCase()) != -1){
let localPath = path;
if(parentPath) {
localPath = `${parentPath} > ${path} `;
}
subMenu.path = localPath;
let selectedNode = pgAdmin.Browser.tree.selected();
if(subMenu.path == 'Object') {
if(selectedNode && selectedNode._metadata.data._type == subMenu.module.parent_type) {
result.push(subMenu);
}
} else {
result.push(subMenu);
}
}
if(subMenu.menu_items) {
iterItem(subMenu.menu_items, getMenuName(subMenu), path);
}
} else {
if(typeof(subMenu) == 'object' && !(subMenu instanceof NewMenuItem || subMenu instanceof pgAdmin.Browser.MenuItem)) {
iterItem(Object.values(subMenu), path, parentPath);
} else {
parseLI(_menu, path);
iterItem(subMenu, path, parentPath);
}
}
});
};
// Expects LI of menus which contains A & UL
const iterItem = (menu, path) => {
const subMenus = Array.from(menu.children);
subMenus.forEach((_menu) => {
if (_menu.tagName == 'UL') {
parseUL(_menu, path);
}
});
};
// Starting Point
const navbar = document.querySelector('.navbar-nav');
const mainMenus = Array.from(navbar.children);
let {name: browser} = getBrowser();
const mainMenus = browser == 'Nwjs' ?pgAdmin.Browser.MainMenus : MainMenus;
if(browser == 'Nwjs') {
mainMenus.forEach((menu) => {
let subMenus = menu.menuItems;
iterItem(subMenus, getMenuName(menu));
});
} else {
mainMenus.forEach((menu) => {
let subMenus = [];
if(menu.name == 'object') {
let selectedNode = pgAdmin.Browser.tree.selected();
if(selectedNode) {
subMenus = pgAdmin.Browser.menus[menu.name][selectedNode._metadata.data._type];
}
} else {
subMenus = pgAdmin.Browser.menus[menu.name];
}
iterItem(Object.values(subMenus), getMenuName(menu));
});
}
mainMenus.forEach((menu) => {
iterItem(menu, getMenuName(menu));
});
setState(state => ({
...state,
fetched: true,
data: result,
}));
}

View File

@@ -51,43 +51,47 @@ export function onlineHelpSearch(param, props) {
let iframeHTML = content.document;
window.pooling = setInterval(() => {
let resultEl = iframeHTML.getElementById('search-results');
let searchResultsH2Tags = resultEl.getElementsByTagName('h2');
let list = resultEl && resultEl.getElementsByTagName('LI');
let pooling = window.pooling;
if ((list && list.length > 0 )) {
let res = extractSearchResult(list);
// After getting the data, we need to call the Parent component function
// which will render the data on the screen
if (searchResultsH2Tags[0]['childNodes'][0]['textContent'] != 'Searching') {
window.clearInterval(pooling);
if(resultEl) {
let searchResultsH2Tags = resultEl.getElementsByTagName('h2');
let list = resultEl && resultEl.getElementsByTagName('LI');
if ((list && list.length > 0 )) {
let res = extractSearchResult(list);
// After getting the data, we need to call the Parent component function
// which will render the data on the screen
if (searchResultsH2Tags[0]['childNodes'][0]['textContent'] != 'Searching') {
window.clearInterval(pooling);
setState(state => ({
...state,
fetched: true,
clearedPooling: true,
url: srcURL,
data: res,
}));
isIFrameLoaded = false;
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
} else {
setState(state => ({
...state,
fetched: true,
clearedPooling: false,
url: srcURL,
data: res,
}));
}
} else if(searchResultsH2Tags[0]['childNodes'][0]['textContent'] == 'Search Results') {
setState(state => ({
...state,
fetched: true,
clearedPooling: true,
url: srcURL,
data: res,
data: {},
}));
isIFrameLoaded = false;
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
} else {
setState(state => ({
...state,
fetched: true,
clearedPooling: false,
url: srcURL,
data: res,
}));
isIFrameLoaded = false;
window.clearInterval(pooling);
}
} else if(searchResultsH2Tags[0]['childNodes'][0]['textContent'] == 'Search Results') {
setState(state => ({
...state,
fetched: true,
clearedPooling: true,
url: srcURL,
data: {},
}));
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
isIFrameLoaded = false;
} else {
window.clearInterval(pooling);
}
}, 500);

View File

@@ -7,12 +7,16 @@
//
//////////////////////////////////////////////////////////////
import React, {useRef,useState, useEffect} from 'react';
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import {useDelayDebounce} from 'sources/custom_hooks';
import {onlineHelpSearch} from './online_help';
import {menuSearch} from './menuitems_help';
import $ from 'jquery';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import pgAdmin from 'sources/pgadmin';
import { getBrowser } from '../../../../static/js/utils';
function HelpArticleContents({isHelpLoading, isMenuLoading, helpSearchResult}) {
return (isHelpLoading && !(isMenuLoading??true)) ? (
@@ -39,8 +43,17 @@ HelpArticleContents.propTypes = {
isMenuLoading: PropTypes.bool
};
export function Search() {
const useModalStyles = makeStyles(() => ({
setTop: {
marginTop: '-20px',
}
}));
export function Search({closeModal}) {
let {name: browser} = getBrowser();
const classes = useModalStyles();
const wrapperRef = useRef(null);
const firstEleRef = useRef();
const [searchTerm, setSearchTerm] = useState('');
const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false);
const [isMenuLoading, setIsMenuLoading] = useState(false);
@@ -64,6 +77,7 @@ export function Search() {
fetched: false,
data: [],
}));
setHelpSearchResult(state => ({
...state,
fetched: false,
@@ -120,37 +134,37 @@ export function Search() {
const refactorMenuItems = (items) => {
if(items.length > 0){
let menuItemsHtmlElement = [];
for(let i=0; i < items.length; i++){
Object.keys(items[i]).forEach( (value) => {
if(value != 'element' && value != 'No object selected'){
menuItemsHtmlElement.push( <li key={ 'li-menu-' + i }><a tabIndex='0' id={ 'li-menu-' + i } href={'#'} className={ (items[i]['element'].classList.contains('disabled') ? 'dropdown-item menu-groups-a disabled':'dropdown-item menu-groups-a')} key={ 'menu-' + i } onClick={() => {items[i]['element'].click(); toggleDropdownMenu();}}>
{value}
<span key={ 'menu-span-' + i }>{refactorPathToMenu(items[i][value])}</span>
</a>
{ ((items[i]['element'].classList.contains('disabled') && items[i]['element'].getAttribute('data-disabled') != undefined) ? <i className='fa fa-info-circle quick-search-tooltip' data-toggle='tooltip' title={items[i]['element'].getAttribute('data-disabled')} aria-label='Test data tooltip' aria-hidden='true'></i> : '' )}
</li>);
}
});
}
items.forEach((i) => {
menuItemsHtmlElement.push(
<li key={ 'li-menu-' + i }><a tabIndex='0' id={ 'li-menu-' + i.label } href={'#'} className={ (i.is_disabled ? 'dropdown-item menu-groups-a disabled':'dropdown-item menu-groups-a')} key={ 'menu-' + i.label } onClick={
() => {
closeModal();
if(browser == 'Nwjs') {
i.callback();
} else {
// Some callbacks registered in 'callbacks' check and call specifiec callback function
if (i.module && 'callbacks' in i.module && i.module.callbacks[i.callback]) {
i.module.callbacks[i.callback].apply(i.module, [i.data, pgAdmin.Browser.tree.selected()]);
} else if (i.module && i.module[i.callback]) {
i.module[i.callback].apply(i.module, [i.data, pgAdmin.Browser.tree.selected()]);
} else if (i.callback) {
i.callback(i);
} else {
window.open(i.url);
}
}
}
}>
{i.label}
<span key={ 'menu-span-' + i.label }>{i.path}</span>
</a>
</li>);
});
$('[data-toggle="tooltip"]').tooltip();
return menuItemsHtmlElement;
}
};
const refactorPathToMenu = (path) => {
if(path){
let pathArray = path.split('/');
let spanElement = [];
for(let i = 0; i < pathArray.length; i++ ){
if(i == (pathArray.length -1)){
spanElement.push(pathArray[i]);
}else{
spanElement.push(<span key={ 'menu-span-sub' + i }> {pathArray[i]} <i className='fa fa-angle-right' aria-hidden='true'></i> </span>);
}
}
return spanElement;
}
};
const onInputValueChange = (value) => {
let pooling = window.pooling;
@@ -210,13 +224,19 @@ export function Search() {
return loading ? <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> : '';
};
useEffect(() => {
setTimeout(() => {
firstEleRef.current && firstEleRef.current.focus();
}, 350);
}, [firstEleRef.current]);
return (
<div id='quick-search-container' onClick={setSearchTerm}></div>,
<ul id='quick-search-container' ref={wrapperRef} className='test' role="menu">
<ul id='quick-search-container' ref={wrapperRef} className={clsx('test', classes.setTop)} role="menu">
<li>
<ul id='myDropdown'>
<li className='dropdown-item-input'>
<input tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field'
<input ref={firstEleRef} tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field'
aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } />
</li>
<div style={{marginBottom:0}}>
@@ -229,7 +249,6 @@ export function Search() {
</div>)
:''}
<div >
{ (menuSearchResult.fetched && !(isMenuLoading??true) ) ?
<div>
<div className='menu-groups'>
@@ -269,3 +288,8 @@ export function Search() {
</ul>
);
}
Search.propTypes = {
closeModal: PropTypes.func
};