public inbox for [email protected]help / color / mirror / Atom feed
Quick search for menu items & help articles 17+ messages / 4 participants [nested] [flat]
* Quick search for menu items & help articles @ 2021-01-14 11:24 Pramod Ahire <[email protected]> 0 siblings, 2 replies; 17+ messages in thread From: Pramod Ahire @ 2021-01-14 11:24 UTC (permalink / raw) To: pgadmin-hackers Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com Attachments: [application/octet-stream] quick_search_v1.patch (33.9K, 3-quick_search_v1.patch) download | inline diff: diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index 640a05468..672b3e646 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -443,6 +443,19 @@ def register_browser_preferences(self): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', 'open_quick_search', + gettext('Quick Search'), 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 70, 'char': 'f'} + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=fields + ) + self.dynamic_tab_title = self.preference.register( 'tab settings', 'dynamic_tabs', gettext("Dynamic tab size"), 'boolean', False, diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 3381162d0..a69c7009f 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value), 'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value), 'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), + 'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), }; this.shortcutMethods = { @@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple 'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row + 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; this.bindShortcuts(); } @@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, { return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut); }, + bindOpenQuickSearch: function() { + $('#search_icon').trigger('click'); + }, }); module.exports = pgAdmin.Browser.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js new file mode 100644 index 000000000..82d95dfd4 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search.js @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, 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); +}; + +// Entry point - Quick search functionality +if (document.getElementById('quick-search-component')) { + ReactDOM.render( + <Search onResult={onResultFetch} />, + document.getElementById('quick-search-component') + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js new file mode 100644 index 000000000..bf9f8c059 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +// Allow us to render IFrame using React +// Here we will add the event listener on Iframe load event +export class Iframe extends Component { + static get propTypes() { + return { + id: PropTypes.string.isRequired, + srcURL: PropTypes.string.isRequired, + onLoad: PropTypes.func.isRequired, + }; + } + + render () { + const iframeStyle = { + border: '0', + display: 'block', + position:'absolute', + opacity:'0', + }; + const {id, srcURL, onLoad} = this.props; + + return ( + <iframe + id={id} + src={srcURL} + onLoad={onLoad} + width={'20'} + height={'20'} + style={iframeStyle} + /> + ); + } +} diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js new file mode 100644 index 000000000..b3225129a --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import gettext from 'sources/gettext'; + +const LAST_MENU = gettext('About pgAdmin 4'); + +// 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; +}; + +export function menuSearch(param, props) { + param = param.trim(); + const setState = props.setState; + let result = []; + // 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); + } else { + parseLI(_menu, path); + } + } + }); + }; + + // 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); + + mainMenus.forEach((menu) => { + iterItem(menu, getMenuName(menu)); + }); +} diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js new file mode 100644 index 000000000..9808ad007 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/online_help.js @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Iframe} from './iframe_component'; +import url_for from 'sources/url_for'; + +const extractSearchResult = (list) => { + let result = {}; + for (let idx = 0; idx < list.length; idx++) { + let link = list[idx].getElementsByTagName('A'); + // we are not going to display more than first 10 result as per design + if (link.length == 0) { + break; + } + let topicName = link[0].text; + let topicLink = url_for('help.static', { + 'filename': link[0].getAttribute('href'), + }); + result[topicName] = topicLink; + } + return result; +}; + +export function onlineHelpSearch(param, props) { + param = param.split(' ').join('+'); + const setState = props.setState; + const helpURL = url_for('help.static', { + 'filename': 'search.html', + }); + const srcURL = `${helpURL}?q=${param}`; + let isIFrameLoaded = false; + if(document.getElementById('hidden-quick-search-iframe')){ + document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true); + } + + // Below function will be called when the page will be loaded in Iframe + const _iframeLoaded = () => { + if (isIFrameLoaded) { + return false; + } + isIFrameLoaded = true; + let iframe = document.getElementById('hidden-quick-search-iframe'); + let content = (iframe.contentWindow || iframe.contentDocument); + 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); + 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: {}, + })); + ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container')); + isIFrameLoaded = false; + window.clearInterval(pooling); + } + }, 500); + }; + + // Render IFrame + ReactDOM.render( + <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>, + document.getElementById('quick-search-iframe-container'), + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js new file mode 100644 index 000000000..46779ae33 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -0,0 +1,251 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, {useState, useEffect} from 'react'; +import {useDelayDebounce} from 'sources/custom_hooks'; +import {onlineHelpSearch} from './online_help'; +import {menuSearch} from './menuitems_help'; +import $ from 'jquery'; +import gettext from 'sources/gettext'; + +export function Search() { + const [searchTerm, setSearchTerm] = useState(''); + const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); + let helpLinkTitles = []; + let helpLinks = []; + const [isMenuLoading, setIsMenuLoading] = useState(false); + const [isHelpLoading, setIsHelpLoading] = useState(false); + const [menuSearchResult, setMenuSearchResult] = useState({ + fetched: false, + data: [], + }); + const [helpSearchResult, setHelpSearchResult] = useState({ + fetched: false, + clearedPooling: true, + url: '', + data: [], + }); + + const [showResults, setShowResults] = useState(false); + + const resetSearchState = () => { + setMenuSearchResult(state => ({ + ...state, + fetched: false, + data: [], + })); + setHelpSearchResult(state => ({ + ...state, + fetched: false, + clearedPooling: true, + url: '', + data: {}, + })); + }; + + // Below will be called when any changes has been made to state + useEffect(() => { + helpLinkTitles = Object.keys(helpSearchResult.data); + for(let i = 0; i<helpLinkTitles.length;i++){ + helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>); + } + + if(menuSearchResult.fetched == true){ + setIsMenuLoading(false); + } + + if(helpSearchResult.fetched == true){ + setIsHelpLoading(false); + } + }, [menuSearchResult, helpSearchResult]); + + const initSearch = (param) => { + setIsMenuLoading(true); + setIsHelpLoading(true); + + onlineHelpSearch(param, { + state: helpSearchResult, + setState: setHelpSearchResult, + }); + menuSearch(param, { + state: menuSearchResult, + setState: setMenuSearchResult, + }); + }; + + + // Debounse logic to avoid multiple re-render with each keypress + useDelayDebounce(initSearch, searchTerm, 1000); + + const toggleDropdownMenu = () => { + let pooling = window.pooling; + if(pooling){ + window.clearInterval(pooling); + } + document.getElementsByClassName('live-search-field')[0].value = ''; + setTimeout(function(){ + document.getElementById('live-search-field').focus(); + },100); + resetSearchState(); + setShowResults(!showResults); + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(false); + }; + + const refactorMenuItems = (items) => { + if(items.length > 0){ + let menuItemsHtmlElement = []; + for(let i=0; i < items.length; i++){ + Object.keys(items[i]).map( (value) => { + if(value != 'element' && value != 'No object selected'){ + menuItemsHtmlElement.push( <li role='menuitem' key={ 'li-menu-' + i }><a id={ 'li-menu-' + i } href={'#'} className='menu-groups-a dropdown-item' key={ 'menu-' + i } onClick={() => {items[i]['element'].click(); toggleDropdownMenu();}}> + {value} + <span key={ 'menu-span-' + i }>{refactorPathToMenu(items[i][value])}</span> + </a></li>); + } + }); + } + 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; + if(pooling){ + window.clearInterval(pooling); + } + resetSearchState(); + setSearchTerm(''); + if(value.length >= 3){ + setSearchTerm(value); + setIsMenuLoading(true); + setIsHelpLoading(true); + setIsShowMinLengthMsg(false); + }else{ + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(true); + } + }; + + const handleKeyDown = ({ keyCode }) => { + if(keyCode == 40){ + $('a:focus').closest('li').next().find('a').first().focus(); + return false; + }else if(keyCode == 38){ + $('a:focus').closest('li').prev().find('a').first().focus(); + return false; + }else if(keyCode == 27){ + toggleDropdownMenu(); + } + }; + + const handleEscKeyPress = ({ keyCode }) => { + if(keyCode == 27){ + toggleDropdownMenu(); + } + }; + + const handleInputKeyDown = ({ keyCode }) => { + if(keyCode == 40){ + $('#li-menu-0').focus(); + } + }; + + return ( + <div id='quick-search-container' onClick={setSearchTerm}></div>, + <div id='quick-search-container' role="menu"> + <div className='custom-dropdown'> + <a href='#'><div tabIndex='0' className='fa fa-search search_icon' id='search_icon' onClick={() => toggleDropdownMenu()} ></div></a> + + <div id='myDropdown' className={showResults ? 'custom-dropdown-content visible' : 'custom-dropdown-content hidden'} onKeyDown={handleEscKeyPress}> + + <input tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field' onKeyDown={handleInputKeyDown} + aria-label='live-search-field' id='live-search-field' placeholder={gettext('Search')} onChange={(e) => {onInputValueChange(e.target.value);} } /> + + <ul onKeyDown={handleKeyDown} style={{marginBottom:0}}> + <div className='content-groups' > + + { isShowMinLengthMsg + ? (<div className='pad-12 no-results'> + <span className='fa fa-info-circle'></span> + Please enter minimum 3 characters to search + </div>) + :''} + <div className='fixed-max-height' > + + { (menuSearchResult.fetched == true && isMenuLoading == false ) ? + <div> + <div className='menu-groups'> + <span className='fa fa-window-maximize'></span> {gettext('MENU ITEMS')} ({menuSearchResult.data.length}) + </div> + + + {refactorMenuItems(menuSearchResult.data)} + </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')} + + {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + + { (helpSearchResult.fetched == true && isHelpLoading == false) ? + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> {gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ? + <span>(10 of {Object.keys(helpSearchResult.data).length} ) + </span>: + '(' + Object.keys(helpSearchResult.data).length + ')'} + { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''} + { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} <span className='fas fa-external-link-alt' ></span></a> : ''} + </div> + + {Object.keys(helpSearchResult.data).map( (value, index) => { + if(index <= 9) { return <li role='menuitem' key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; } + })} + + {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + </div> : ( (isHelpLoading && isMenuLoading == false) ? ( + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> + HELP ARTICLES + {Object.keys(helpSearchResult.data).length > 10 + ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')' + : '(' + Object.keys(helpSearchResult.data).length + ')' + } + { Object.keys(helpSearchResult.data).length > 10 + ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'> + Show all <span className='fas fa-external-link-alt' ></span></a> : '' + } + </div> + <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> + </div>) : '')} + </div> + </div> + </ul> + </div> + </div> + <div id='quick-search-iframe-container' /> + </div> + ); + +} diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss new file mode 100644 index 000000000..4b505a090 --- /dev/null +++ b/web/pgadmin/browser/static/scss/_quick_search.scss @@ -0,0 +1,137 @@ +#myInput { + box-sizing: border-box; + background-position: 14px 12px; + background-repeat: no-repeat; + font-size: 16px; + padding: 14px 20px 12px 45px; + border: none; + border-bottom: 1px solid #ddd; +} + +#myInput:focus {outline: 3px solid #ddd;} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + display: none; + position: absolute; + background-color: $color-bg; + min-width: 376px; + overflow: auto; + border: 1px solid #ddd; + z-index: 1; + right:0.10em; + border-radius: 4px; + box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.2); +} + +.custom-dropdown-content a { + color: $dropdown-link-color; + padding: 6px 12px 6px 21px; + text-decoration: none; + display: block; + cursor:pointer; +} + +.custom-dropdown-content a:hover { + color: $black; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$quick-search-a-text-color ;} + +.search_icon{ + color: $white; + cursor: pointer; + padding-right: 8px; + border-right: 2px solid #fff; +} +.hidden { display:none; } + +.visible { display:block; } + +.live-search-field{ + margin: 6px; + width: 360px !important; + position: fixed; +} + +.menu-groups, .help-groups{ + background-color: $color-gray-light; + padding: 6px; + font-size: 12px; + font-weight: 600; +} + +.menu-groups .fa, .fas{ + font-weight:normal !important; +} + +.help-groups .fa, .fas{ + font-weight:600 !important; +} + +.pad-12{ + padding:12px; +} + +.no-results{ + font-size: 14px; + color: #697986; + text-align: center; +} + +.no-padding{ + padding:0 !important; +} + +.menu-groups-a{ + display:flex !important; + flex-direction:column; +} + +.menu-groups-a span{ + font-size: 0.9em; + font-weight: 100; + color: $quick-search-span-text; +} + +.menu-groups-a:hover span{ + color: $quick-search-span-text-hover ; +} + +.menu-groups-a:hover span{ + color: $quick-search-span-text; +} + +.content-groups{ + margin-top: 3.0em; +} + +.fixed-max-height{ + max-height: 90vh; + overflow-y: auto; +} + +.search-icon{ + background: $loader-icon-small center center no-repeat; + margin: auto !important; + height: 22px !important; + width: 130px !important; + background-position: left !important; + font-size: 14px; + color: $dropdown-link-color; + padding-left: 30px; +} + +.help_loading_icon{ + height: 16px; +} +#myDropdown ul { + list-style: none; +} + + + diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index c571df246..328d7e63d 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -136,6 +136,9 @@ window.onload = function(e){ <ul class="dropdown-menu" role="menu"></ul> </li> </ul> + + <div id="quick-search-component"></div> + {% if config.SERVER_MODE %} <ul class="navbar-nav"> <li class="nav-item active dropdown"> diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 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 Bt<?Yk literal 0 HcmV?d00001 diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 9163f0d6c..cc59c1c66 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -26,4 +26,15 @@ export function usePrevious(value) { ref.current = value; }); return ref.current; -} \ No newline at end of file +} + +export function useDelayDebounce(callback, args, delay) { + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (args) { + callback(args); + } + }, delay); + return () => clearTimeout(delayDebounceFn); + }, [args]); +} diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss index fadd71b7b..33f2d108b 100644 --- a/web/pgadmin/static/scss/_pgadmin.style.scss +++ b/web/pgadmin/static/scss/_pgadmin.style.scss @@ -1123,3 +1123,9 @@ select:-webkit-autofill:focus { .pull-left{ float:left } + +.menu-groups-a:hover span, .menu-groups-a:focus span{ + color: $white !important; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;} diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss index 9d913788d..9a56f1c99 100644 --- a/web/pgadmin/static/scss/resources/_default.variables.scss +++ b/web/pgadmin/static/scss/resources/_default.variables.scss @@ -4,6 +4,9 @@ $enable-flex: true; $white: #fff; $black: #000; +$span-text-color: #6B6B6B !default; +$span-text-color-hover: #6B6B6B !default; + $color-bg: $white !default; $color-fg: #222222 !default; @@ -349,3 +352,6 @@ $grid-hover-fg-color: $color-fg !default; $btn-copied-color-fg: $active-color !default; +$quick-search-a-text-color: $black !default; +$quick-search-span-text: $span-text-color !default; +$quick-search-span-text-hover: $span-text-color-hover !default; diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss index 129b9a214..12a2b87df 100644 --- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss @@ -121,3 +121,7 @@ $color-success-hover-fg: $color-fg; $datagrid-selected-color: $color-primary-fg; $select2-placeholder: #999; + +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $white !default; +$quick-search-a-text-color: $white !default; diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss index b895a1223..6459e5714 100644 --- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss @@ -202,4 +202,6 @@ $grid-hover-fg-color: #010B15; $btn-copied-color-fg: #010B15; - +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $black !default; +$quick-search-a-text-color: $black !default; diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js new file mode 100644 index 000000000..d7f59563f --- /dev/null +++ b/web/regression/javascript/quick_search/quick_search_spec.js @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { Search } from 'browser/quick_search/trigger_search'; + +let container; + +describe('quick search test cases', function () { + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + ReactDOM.render(<Search />, container); + }); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should have rendered quick-search-container', () => { + expect(container.firstChild.id).toEqual('quick-search-container'); + }); + + it('should have 2 childs in quick-search-container', () => { + expect(container.firstChild.childNodes.length).toEqual(2); + }); + + it('element should be html element', () => { + let inputElement = document.getElementById('live-search-field'); + expect(inputElement instanceof HTMLElement).toBeTruthy(); + }); + +}); diff --git a/web/webpack.config.js b/web/webpack.config.js index 96ef708ab..e6655512a 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -421,6 +421,7 @@ module.exports = [{ use: { loader: 'imports-loader?' + 'pgadmin.dashboard' + + ',pgadmin.browser.quick_search' + ',pgadmin.tools.user_management' + ',pgadmin.browser.object_statistics' + ',pgadmin.browser.dependencies' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 6fc26021e..bddcda9b8 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -181,6 +181,7 @@ var webpackShimConfig = { 'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'), 'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'), 'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'), + 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), [image/png] Screenshot 2021-01-14 at 4.07.02 PM.png (307.3K, 4-Screenshot%202021-01-14%20at%204.07.02%20PM.png) download | view image [image/png] Screenshot 2021-01-14 at 4.07.45 PM.png (375.7K, 5-Screenshot%202021-01-14%20at%204.07.45%20PM.png) download | view image [image/png] Screenshot 2021-01-14 at 4.08.03 PM.png (311.9K, 6-Screenshot%202021-01-14%20at%204.08.03%20PM.png) download | view image [image/png] Screenshot 2021-01-14 at 4.08.15 PM.png (366.8K, 7-Screenshot%202021-01-14%20at%204.08.15%20PM.png) download | view image [image/png] image001.png (68.4K, 8-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-14 16:55 Dave Page <[email protected]> parent: Pramod Ahire <[email protected]> 1 sibling, 1 reply; 17+ messages in thread From: Dave Page @ 2021-01-14 16:55 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: pgadmin-hackers Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 06:44 Akshay Joshi <[email protected]> parent: Pramod Ahire <[email protected]> 1 sibling, 1 reply; 17+ messages in thread From: Akshay Joshi @ 2021-01-15 06:44 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: pgadmin-hackers Hi Pramod Following are the review comments: - linter issue "Undefined variable $black" - In Desktop mode remove the separator beside the search icon. On Thu, Jan 14, 2021 at 10:18 PM Pramod Ahire <[email protected]> wrote: > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 07:23 Pramod Ahire <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Pramod Ahire @ 2021-01-15 07:23 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: pgadmin-hackers Hi Dave, Thank you for comments ! I had described points below. Please do let me know if anything missing. From: Dave Page <[email protected]> Date: Thursday, 14 January 2021 at 10:26 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). - As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this. 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? - As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading. Please do let me know your valuable inputs on this. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 07:24 Pramod Ahire <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 0 replies; 17+ messages in thread From: Pramod Ahire @ 2021-01-15 07:24 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, Thanks for your quick review ! Made changes as per your comments & attached patch with this email. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Friday, 15 January 2021 at 12:14 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Pramod Following are the review comments: linter issue "Undefined variable $black" In Desktop mode remove the separator beside the search icon. On Thu, Jan 14, 2021 at 10:18 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 Attachments: [application/octet-stream] quick_search_pgadmin_v2.patch (34.3K, 3-quick_search_pgadmin_v2.patch) download | inline diff: diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index 640a05468..672b3e646 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -443,6 +443,19 @@ def register_browser_preferences(self): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', 'open_quick_search', + gettext('Quick Search'), 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 70, 'char': 'f'} + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=fields + ) + self.dynamic_tab_title = self.preference.register( 'tab settings', 'dynamic_tabs', gettext("Dynamic tab size"), 'boolean', False, diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 3381162d0..a69c7009f 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value), 'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value), 'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), + 'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), }; this.shortcutMethods = { @@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple 'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row + 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; this.bindShortcuts(); } @@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, { return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut); }, + bindOpenQuickSearch: function() { + $('#search_icon').trigger('click'); + }, }); module.exports = pgAdmin.Browser.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js new file mode 100644 index 000000000..82d95dfd4 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search.js @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, 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); +}; + +// Entry point - Quick search functionality +if (document.getElementById('quick-search-component')) { + ReactDOM.render( + <Search onResult={onResultFetch} />, + document.getElementById('quick-search-component') + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js new file mode 100644 index 000000000..bf9f8c059 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +// Allow us to render IFrame using React +// Here we will add the event listener on Iframe load event +export class Iframe extends Component { + static get propTypes() { + return { + id: PropTypes.string.isRequired, + srcURL: PropTypes.string.isRequired, + onLoad: PropTypes.func.isRequired, + }; + } + + render () { + const iframeStyle = { + border: '0', + display: 'block', + position:'absolute', + opacity:'0', + }; + const {id, srcURL, onLoad} = this.props; + + return ( + <iframe + id={id} + src={srcURL} + onLoad={onLoad} + width={'20'} + height={'20'} + style={iframeStyle} + /> + ); + } +} diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js new file mode 100644 index 000000000..b3225129a --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import gettext from 'sources/gettext'; + +const LAST_MENU = gettext('About pgAdmin 4'); + +// 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; +}; + +export function menuSearch(param, props) { + param = param.trim(); + const setState = props.setState; + let result = []; + // 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); + } else { + parseLI(_menu, path); + } + } + }); + }; + + // 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); + + mainMenus.forEach((menu) => { + iterItem(menu, getMenuName(menu)); + }); +} diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js new file mode 100644 index 000000000..9808ad007 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/online_help.js @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Iframe} from './iframe_component'; +import url_for from 'sources/url_for'; + +const extractSearchResult = (list) => { + let result = {}; + for (let idx = 0; idx < list.length; idx++) { + let link = list[idx].getElementsByTagName('A'); + // we are not going to display more than first 10 result as per design + if (link.length == 0) { + break; + } + let topicName = link[0].text; + let topicLink = url_for('help.static', { + 'filename': link[0].getAttribute('href'), + }); + result[topicName] = topicLink; + } + return result; +}; + +export function onlineHelpSearch(param, props) { + param = param.split(' ').join('+'); + const setState = props.setState; + const helpURL = url_for('help.static', { + 'filename': 'search.html', + }); + const srcURL = `${helpURL}?q=${param}`; + let isIFrameLoaded = false; + if(document.getElementById('hidden-quick-search-iframe')){ + document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true); + } + + // Below function will be called when the page will be loaded in Iframe + const _iframeLoaded = () => { + if (isIFrameLoaded) { + return false; + } + isIFrameLoaded = true; + let iframe = document.getElementById('hidden-quick-search-iframe'); + let content = (iframe.contentWindow || iframe.contentDocument); + 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); + 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: {}, + })); + ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container')); + isIFrameLoaded = false; + window.clearInterval(pooling); + } + }, 500); + }; + + // Render IFrame + ReactDOM.render( + <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>, + document.getElementById('quick-search-iframe-container'), + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js new file mode 100644 index 000000000..46779ae33 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -0,0 +1,251 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, {useState, useEffect} from 'react'; +import {useDelayDebounce} from 'sources/custom_hooks'; +import {onlineHelpSearch} from './online_help'; +import {menuSearch} from './menuitems_help'; +import $ from 'jquery'; +import gettext from 'sources/gettext'; + +export function Search() { + const [searchTerm, setSearchTerm] = useState(''); + const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); + let helpLinkTitles = []; + let helpLinks = []; + const [isMenuLoading, setIsMenuLoading] = useState(false); + const [isHelpLoading, setIsHelpLoading] = useState(false); + const [menuSearchResult, setMenuSearchResult] = useState({ + fetched: false, + data: [], + }); + const [helpSearchResult, setHelpSearchResult] = useState({ + fetched: false, + clearedPooling: true, + url: '', + data: [], + }); + + const [showResults, setShowResults] = useState(false); + + const resetSearchState = () => { + setMenuSearchResult(state => ({ + ...state, + fetched: false, + data: [], + })); + setHelpSearchResult(state => ({ + ...state, + fetched: false, + clearedPooling: true, + url: '', + data: {}, + })); + }; + + // Below will be called when any changes has been made to state + useEffect(() => { + helpLinkTitles = Object.keys(helpSearchResult.data); + for(let i = 0; i<helpLinkTitles.length;i++){ + helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>); + } + + if(menuSearchResult.fetched == true){ + setIsMenuLoading(false); + } + + if(helpSearchResult.fetched == true){ + setIsHelpLoading(false); + } + }, [menuSearchResult, helpSearchResult]); + + const initSearch = (param) => { + setIsMenuLoading(true); + setIsHelpLoading(true); + + onlineHelpSearch(param, { + state: helpSearchResult, + setState: setHelpSearchResult, + }); + menuSearch(param, { + state: menuSearchResult, + setState: setMenuSearchResult, + }); + }; + + + // Debounse logic to avoid multiple re-render with each keypress + useDelayDebounce(initSearch, searchTerm, 1000); + + const toggleDropdownMenu = () => { + let pooling = window.pooling; + if(pooling){ + window.clearInterval(pooling); + } + document.getElementsByClassName('live-search-field')[0].value = ''; + setTimeout(function(){ + document.getElementById('live-search-field').focus(); + },100); + resetSearchState(); + setShowResults(!showResults); + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(false); + }; + + const refactorMenuItems = (items) => { + if(items.length > 0){ + let menuItemsHtmlElement = []; + for(let i=0; i < items.length; i++){ + Object.keys(items[i]).map( (value) => { + if(value != 'element' && value != 'No object selected'){ + menuItemsHtmlElement.push( <li role='menuitem' key={ 'li-menu-' + i }><a id={ 'li-menu-' + i } href={'#'} className='menu-groups-a dropdown-item' key={ 'menu-' + i } onClick={() => {items[i]['element'].click(); toggleDropdownMenu();}}> + {value} + <span key={ 'menu-span-' + i }>{refactorPathToMenu(items[i][value])}</span> + </a></li>); + } + }); + } + 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; + if(pooling){ + window.clearInterval(pooling); + } + resetSearchState(); + setSearchTerm(''); + if(value.length >= 3){ + setSearchTerm(value); + setIsMenuLoading(true); + setIsHelpLoading(true); + setIsShowMinLengthMsg(false); + }else{ + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(true); + } + }; + + const handleKeyDown = ({ keyCode }) => { + if(keyCode == 40){ + $('a:focus').closest('li').next().find('a').first().focus(); + return false; + }else if(keyCode == 38){ + $('a:focus').closest('li').prev().find('a').first().focus(); + return false; + }else if(keyCode == 27){ + toggleDropdownMenu(); + } + }; + + const handleEscKeyPress = ({ keyCode }) => { + if(keyCode == 27){ + toggleDropdownMenu(); + } + }; + + const handleInputKeyDown = ({ keyCode }) => { + if(keyCode == 40){ + $('#li-menu-0').focus(); + } + }; + + return ( + <div id='quick-search-container' onClick={setSearchTerm}></div>, + <div id='quick-search-container' role="menu"> + <div className='custom-dropdown'> + <a href='#'><div tabIndex='0' className='fa fa-search search_icon' id='search_icon' onClick={() => toggleDropdownMenu()} ></div></a> + + <div id='myDropdown' className={showResults ? 'custom-dropdown-content visible' : 'custom-dropdown-content hidden'} onKeyDown={handleEscKeyPress}> + + <input tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field' onKeyDown={handleInputKeyDown} + aria-label='live-search-field' id='live-search-field' placeholder={gettext('Search')} onChange={(e) => {onInputValueChange(e.target.value);} } /> + + <ul onKeyDown={handleKeyDown} style={{marginBottom:0}}> + <div className='content-groups' > + + { isShowMinLengthMsg + ? (<div className='pad-12 no-results'> + <span className='fa fa-info-circle'></span> + Please enter minimum 3 characters to search + </div>) + :''} + <div className='fixed-max-height' > + + { (menuSearchResult.fetched == true && isMenuLoading == false ) ? + <div> + <div className='menu-groups'> + <span className='fa fa-window-maximize'></span> {gettext('MENU ITEMS')} ({menuSearchResult.data.length}) + </div> + + + {refactorMenuItems(menuSearchResult.data)} + </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')} + + {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + + { (helpSearchResult.fetched == true && isHelpLoading == false) ? + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> {gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ? + <span>(10 of {Object.keys(helpSearchResult.data).length} ) + </span>: + '(' + Object.keys(helpSearchResult.data).length + ')'} + { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''} + { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} <span className='fas fa-external-link-alt' ></span></a> : ''} + </div> + + {Object.keys(helpSearchResult.data).map( (value, index) => { + if(index <= 9) { return <li role='menuitem' key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; } + })} + + {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + </div> : ( (isHelpLoading && isMenuLoading == false) ? ( + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> + HELP ARTICLES + {Object.keys(helpSearchResult.data).length > 10 + ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')' + : '(' + Object.keys(helpSearchResult.data).length + ')' + } + { Object.keys(helpSearchResult.data).length > 10 + ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'> + Show all <span className='fas fa-external-link-alt' ></span></a> : '' + } + </div> + <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> + </div>) : '')} + </div> + </div> + </ul> + </div> + </div> + <div id='quick-search-iframe-container' /> + </div> + ); + +} diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss new file mode 100644 index 000000000..00c67405b --- /dev/null +++ b/web/pgadmin/browser/static/scss/_quick_search.scss @@ -0,0 +1,138 @@ +#myInput { + box-sizing: border-box; + background-position: 14px 12px; + background-repeat: no-repeat; + font-size: 16px; + padding: 14px 20px 12px 45px; + border: none; + border-bottom: 1px solid #ddd; +} + +#myInput:focus {outline: 3px solid #ddd;} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + display: none; + position: absolute; + background-color: $color-bg; + min-width: 376px; + overflow: auto; + border: 1px solid #ddd; + z-index: 1; + right:0.10em; + border-radius: 4px; + box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.2); +} + +.custom-dropdown-content a { + color: $dropdown-link-color; + padding: 6px 12px 6px 21px; + text-decoration: none; + display: block; + cursor:pointer; +} + +.custom-dropdown-content a:hover { + color: $black; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$quick-search-a-text-color ;} + +.search_icon{ + color: $white; + cursor: pointer; + padding-right: 8px; +} +.hidden { display:none; } + +.visible { display:block; } + +.live-search-field{ + margin: 6px; + width: 360px !important; + position: fixed; +} + +.menu-groups, .help-groups{ + background-color: $color-gray-light; + padding: 6px; + font-size: 12px; + font-weight: 600; +} + +.menu-groups .fa, .fas{ + font-weight:normal !important; +} + +.help-groups .fa, .fas{ + font-weight:600 !important; +} + +.pad-12{ + padding:12px; +} + +.no-results{ + font-size: 14px; + color: #697986; + text-align: center; +} + +.no-padding{ + padding:0 !important; +} + +.menu-groups-a{ + display:flex !important; + flex-direction:column; +} + +.menu-groups-a span{ + font-size: 0.9em; + font-weight: 100; + color: $quick-search-span-text; +} + +.menu-groups-a:hover span{ + color: $quick-search-span-text-hover ; +} + +.menu-groups-a:hover span{ + color: $quick-search-span-text; +} + +.content-groups{ + margin-top: 3.0em; +} + +.fixed-max-height{ + max-height: 90vh; + overflow-y: auto; +} + +.search-icon{ + background: $loader-icon-small center center no-repeat; + margin: auto !important; + height: 22px !important; + width: 130px !important; + background-position: left !important; + font-size: 14px; + color: $dropdown-link-color; + padding-left: 30px; +} + +.help_loading_icon{ + height: 16px; +} +#myDropdown ul { + list-style: none; +} + +.border-right-search-icon{ + border-right: 2px solid #fff; +} + diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index c571df246..b0d4b08bf 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -136,7 +136,13 @@ window.onload = function(e){ <ul class="dropdown-menu" role="menu"></ul> </li> </ul> + + {% if config.SERVER_MODE != True %} + <div id="quick-search-component" ></div> + {% endif %} + {% if config.SERVER_MODE %} + <div id="quick-search-component" class="border-right-search-icon"></div> <ul class="navbar-nav"> <li class="nav-item active dropdown"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 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 Bt<?Yk literal 0 HcmV?d00001 diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 9163f0d6c..cc59c1c66 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -26,4 +26,15 @@ export function usePrevious(value) { ref.current = value; }); return ref.current; -} \ No newline at end of file +} + +export function useDelayDebounce(callback, args, delay) { + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (args) { + callback(args); + } + }, delay); + return () => clearTimeout(delayDebounceFn); + }, [args]); +} diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss index fadd71b7b..33f2d108b 100644 --- a/web/pgadmin/static/scss/_pgadmin.style.scss +++ b/web/pgadmin/static/scss/_pgadmin.style.scss @@ -1123,3 +1123,9 @@ select:-webkit-autofill:focus { .pull-left{ float:left } + +.menu-groups-a:hover span, .menu-groups-a:focus span{ + color: $white !important; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;} diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss index 9d913788d..9a56f1c99 100644 --- a/web/pgadmin/static/scss/resources/_default.variables.scss +++ b/web/pgadmin/static/scss/resources/_default.variables.scss @@ -4,6 +4,9 @@ $enable-flex: true; $white: #fff; $black: #000; +$span-text-color: #6B6B6B !default; +$span-text-color-hover: #6B6B6B !default; + $color-bg: $white !default; $color-fg: #222222 !default; @@ -349,3 +352,6 @@ $grid-hover-fg-color: $color-fg !default; $btn-copied-color-fg: $active-color !default; +$quick-search-a-text-color: $black !default; +$quick-search-span-text: $span-text-color !default; +$quick-search-span-text-hover: $span-text-color-hover !default; diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss index 129b9a214..12a2b87df 100644 --- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss @@ -121,3 +121,7 @@ $color-success-hover-fg: $color-fg; $datagrid-selected-color: $color-primary-fg; $select2-placeholder: #999; + +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $white !default; +$quick-search-a-text-color: $white !default; diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss index b895a1223..3885994cc 100644 --- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss @@ -1,4 +1,5 @@ $white: #FFFFFF; +$black: #000; $color-bg: #010B15; $color-fg: $white; @@ -202,4 +203,6 @@ $grid-hover-fg-color: #010B15; $btn-copied-color-fg: #010B15; - +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $black !default; +$quick-search-a-text-color: $black !default; diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js new file mode 100644 index 000000000..d7f59563f --- /dev/null +++ b/web/regression/javascript/quick_search/quick_search_spec.js @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { Search } from 'browser/quick_search/trigger_search'; + +let container; + +describe('quick search test cases', function () { + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + ReactDOM.render(<Search />, container); + }); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should have rendered quick-search-container', () => { + expect(container.firstChild.id).toEqual('quick-search-container'); + }); + + it('should have 2 childs in quick-search-container', () => { + expect(container.firstChild.childNodes.length).toEqual(2); + }); + + it('element should be html element', () => { + let inputElement = document.getElementById('live-search-field'); + expect(inputElement instanceof HTMLElement).toBeTruthy(); + }); + +}); diff --git a/web/webpack.config.js b/web/webpack.config.js index 96ef708ab..e6655512a 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -421,6 +421,7 @@ module.exports = [{ use: { loader: 'imports-loader?' + 'pgadmin.dashboard' + + ',pgadmin.browser.quick_search' + ',pgadmin.tools.user_management' + ',pgadmin.browser.object_statistics' + ',pgadmin.browser.dependencies' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 6fc26021e..bddcda9b8 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -181,6 +181,7 @@ var webpackShimConfig = { 'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'), 'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'), 'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'), + 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), [image/png] image001.png (68.4K, 4-image001.png) download | view image [image/png] image002.png (68.4K, 5-image002.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 09:16 Dave Page <[email protected]> parent: Pramod Ahire <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Dave Page @ 2021-01-15 09:16 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: pgadmin-hackers Hi On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote: > Hi Dave, > > > > Thank you for comments ! I had described points below. Please do let me > know if anything missing. > > > > *From: *Dave Page <[email protected]> > *Date: *Thursday, 14 January 2021 at 10:26 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi > > > > On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > > > > Looks very good. I haven't done an extensive code review/test, but two > things spring to mind immediately: > > > > 1) I think the search box should be the top item on the Help menu. I do > not think it should be on the far end of the menu bar, as it looks too much > like it will search for data (think of search on a website). > > > > - As we are showing menu items as well in search results, it can be > redundant for end user & increase duplications of menu items. Please advise > your thoughts on this. > The search box could be on the Help menu, with results shown in a sub-menu under it. Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default. > > > 2) Do we need another loading icon? Surely there's one in the source tree > already that we can use? > > > > - As we are loading help articles count in background, so I have added > background loading icon to show near to count of results. Our existing icon > is of blue & white circle combination, which will be more useful to show > foreground loading. > Hmm, OK. > > > Please do let me know your valuable inputs on this. > > > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > > > > -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 09:43 Akshay Joshi <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Akshay Joshi @ 2021-01-15 09:43 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: Pramod Ahire <[email protected]>; pgadmin-hackers Hi Dave On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: > Hi > > On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < > [email protected]> wrote: > >> Hi Dave, >> >> >> >> Thank you for comments ! I had described points below. Please do let me >> know if anything missing. >> >> >> >> *From: *Dave Page <[email protected]> >> *Date: *Thursday, 14 January 2021 at 10:26 PM >> *To: *Pramod Ahire <[email protected]> >> *Cc: *pgadmin-hackers <[email protected]> >> *Subject: *Re: Quick search for menu items & help articles >> >> >> >> Hi >> >> >> >> On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < >> [email protected]> wrote: >> >> Hi Team, >> >> >> >> Please find the attached designs & patch that contains complete >> functionality except below to do for quick search. >> >> >> >> To Do: >> >> >> >> - Unit test cases are not that sufficient to cover complete code, but >> will be working in background to cover up those one >> - In pgadmin, for disabled menu items we need to add info that will >> describe why menu has disabled & how it will be enabled. Either another way >> to enable all of them & show respective reason in popup that menu is >> disabled for. >> >> >> >> Please do let me know if I missed anything or suggestion of yours. >> >> >> >> Looks very good. I haven't done an extensive code review/test, but two >> things spring to mind immediately: >> >> >> >> 1) I think the search box should be the top item on the Help menu. I do >> not think it should be on the far end of the menu bar, as it looks too much >> like it will search for data (think of search on a website). >> >> >> >> - As we are showing menu items as well in search results, it can be >> redundant for end user & increase duplications of menu items. Please advise >> your thoughts on this. >> > > The search box could be on the Help menu, with results shown in a sub-menu > under it. > > Another more simple option would be to move the search icon to be directly > after the Help menu (which would hint that it's related to the menus), but > I'm also concerned about the number of items on the top menu - we're > getting to the point that 'common' window sizes would show the hamburger > menu by default. > I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu. > > >> >> >> 2) Do we need another loading icon? Surely there's one in the source tree >> already that we can use? >> >> >> >> - As we are loading help articles count in background, so I have added >> background loading icon to show near to count of results. Our existing icon >> is of blue & white circle combination, which will be more useful to show >> foreground loading. >> > > Hmm, OK. > > >> >> >> Please do let me know your valuable inputs on this. >> >> >> >> >> >> *Pramod Ahire* >> >> *Software Engineer* >> >> >> >> [image: >> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >> >> >> >> C: +91-020-66449600/601 >> >> D: +91-9028697679 >> >> *edbpostgres.com <http://edbpostgres.com>* >> >> >> >> >> >> >> >> >> > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 09:45 Dave Page <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Dave Page @ 2021-01-15 09:45 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: Pramod Ahire <[email protected]>; pgadmin-hackers On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote: > Hi Dave > > On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: > >> Hi >> >> On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < >> [email protected]> wrote: >> >>> Hi Dave, >>> >>> >>> >>> Thank you for comments ! I had described points below. Please do let me >>> know if anything missing. >>> >>> >>> >>> *From: *Dave Page <[email protected]> >>> *Date: *Thursday, 14 January 2021 at 10:26 PM >>> *To: *Pramod Ahire <[email protected]> >>> *Cc: *pgadmin-hackers <[email protected]> >>> *Subject: *Re: Quick search for menu items & help articles >>> >>> >>> >>> Hi >>> >>> >>> >>> On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < >>> [email protected]> wrote: >>> >>> Hi Team, >>> >>> >>> >>> Please find the attached designs & patch that contains complete >>> functionality except below to do for quick search. >>> >>> >>> >>> To Do: >>> >>> >>> >>> - Unit test cases are not that sufficient to cover complete code, >>> but will be working in background to cover up those one >>> - In pgadmin, for disabled menu items we need to add info that will >>> describe why menu has disabled & how it will be enabled. Either another way >>> to enable all of them & show respective reason in popup that menu is >>> disabled for. >>> >>> >>> >>> Please do let me know if I missed anything or suggestion of yours. >>> >>> >>> >>> Looks very good. I haven't done an extensive code review/test, but two >>> things spring to mind immediately: >>> >>> >>> >>> 1) I think the search box should be the top item on the Help menu. I do >>> not think it should be on the far end of the menu bar, as it looks too much >>> like it will search for data (think of search on a website). >>> >>> >>> >>> - As we are showing menu items as well in search results, it can be >>> redundant for end user & increase duplications of menu items. Please advise >>> your thoughts on this. >>> >> >> The search box could be on the Help menu, with results shown in a >> sub-menu under it. >> >> Another more simple option would be to move the search icon to be >> directly after the Help menu (which would hint that it's related to the >> menus), but I'm also concerned about the number of items on the top menu - >> we're getting to the point that 'common' window sizes would show the >> hamburger menu by default. >> > > I personally feel we should not add the search box on the Help menu, > it should be kept separate with a placeholder indicating what is it for. We > can move the search icon after the Help menu. > What is your reasoning? It is, after all, specifically designed to help people. > >> >>> >>> >>> 2) Do we need another loading icon? Surely there's one in the source >>> tree already that we can use? >>> >>> >>> >>> - As we are loading help articles count in background, so I have added >>> background loading icon to show near to count of results. Our existing icon >>> is of blue & white circle combination, which will be more useful to show >>> foreground loading. >>> >> >> Hmm, OK. >> >> >>> >>> >>> Please do let me know your valuable inputs on this. >>> >>> >>> >>> >>> >>> *Pramod Ahire* >>> >>> *Software Engineer* >>> >>> >>> >>> [image: >>> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >>> >>> >>> >>> C: +91-020-66449600/601 >>> >>> D: +91-9028697679 >>> >>> *edbpostgres.com <http://edbpostgres.com>* >>> >>> >>> >>> >>> >>> >>> >>> >>> >> >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EDB: http://www.enterprisedb.com >> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Principal Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-15 10:04 Akshay Joshi <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Akshay Joshi @ 2021-01-15 10:04 UTC (permalink / raw) To: Dave Page <[email protected]>; +Cc: Pramod Ahire <[email protected]>; pgadmin-hackers On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: > > > On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi < > [email protected]> wrote: > >> Hi Dave >> >> On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: >> >>> Hi >>> >>> On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < >>> [email protected]> wrote: >>> >>>> Hi Dave, >>>> >>>> >>>> >>>> Thank you for comments ! I had described points below. Please do let me >>>> know if anything missing. >>>> >>>> >>>> >>>> *From: *Dave Page <[email protected]> >>>> *Date: *Thursday, 14 January 2021 at 10:26 PM >>>> *To: *Pramod Ahire <[email protected]> >>>> *Cc: *pgadmin-hackers <[email protected]> >>>> *Subject: *Re: Quick search for menu items & help articles >>>> >>>> >>>> >>>> Hi >>>> >>>> >>>> >>>> On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < >>>> [email protected]> wrote: >>>> >>>> Hi Team, >>>> >>>> >>>> >>>> Please find the attached designs & patch that contains complete >>>> functionality except below to do for quick search. >>>> >>>> >>>> >>>> To Do: >>>> >>>> >>>> >>>> - Unit test cases are not that sufficient to cover complete code, >>>> but will be working in background to cover up those one >>>> - In pgadmin, for disabled menu items we need to add info that will >>>> describe why menu has disabled & how it will be enabled. Either another way >>>> to enable all of them & show respective reason in popup that menu is >>>> disabled for. >>>> >>>> >>>> >>>> Please do let me know if I missed anything or suggestion of yours. >>>> >>>> >>>> >>>> Looks very good. I haven't done an extensive code review/test, but two >>>> things spring to mind immediately: >>>> >>>> >>>> >>>> 1) I think the search box should be the top item on the Help menu. I do >>>> not think it should be on the far end of the menu bar, as it looks too much >>>> like it will search for data (think of search on a website). >>>> >>>> >>>> >>>> - As we are showing menu items as well in search results, it can be >>>> redundant for end user & increase duplications of menu items. Please advise >>>> your thoughts on this. >>>> >>> >>> The search box could be on the Help menu, with results shown in a >>> sub-menu under it. >>> >>> Another more simple option would be to move the search icon to be >>> directly after the Help menu (which would hint that it's related to the >>> menus), but I'm also concerned about the number of items on the top menu - >>> we're getting to the point that 'common' window sizes would show the >>> hamburger menu by default. >>> >> >> I personally feel we should not add the search box on the Help menu, >> it should be kept separate with a placeholder indicating what is it for. We >> can move the search icon after the Help menu. >> > > What is your reasoning? It is, after all, specifically designed to help > people. > OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. > > >> >>> >>>> >>>> >>>> 2) Do we need another loading icon? Surely there's one in the source >>>> tree already that we can use? >>>> >>>> >>>> >>>> - As we are loading help articles count in background, so I have added >>>> background loading icon to show near to count of results. Our existing icon >>>> is of blue & white circle combination, which will be more useful to show >>>> foreground loading. >>>> >>> >>> Hmm, OK. >>> >>> >>>> >>>> >>>> Please do let me know your valuable inputs on this. >>>> >>>> >>>> >>>> >>>> >>>> *Pramod Ahire* >>>> >>>> *Software Engineer* >>>> >>>> >>>> >>>> [image: >>>> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >>>> >>>> >>>> >>>> C: +91-020-66449600/601 >>>> >>>> D: +91-9028697679 >>>> >>>> *edbpostgres.com <http://edbpostgres.com>* >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>> >>> >>> -- >>> Dave Page >>> Blog: http://pgsnake.blogspot.com >>> Twitter: @pgsnake >>> >>> EDB: http://www.enterprisedb.com >>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Principal Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-20 10:49 Pramod Ahire <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Pramod Ahire @ 2021-01-20 10:49 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; Dave Page <[email protected]>; +Cc: pgadmin-hackers Hi Team, As per inputs from you all, made changes & attached patch with this email. Kindly please have a review & do let me know if missed anything. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Friday, 15 January 2021 at 3:34 PM To: Dave Page <[email protected]> Cc: Pramod Ahire <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote: Hi Dave On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: Hi On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote: Hi Dave, Thank you for comments ! I had described points below. Please do let me know if anything missing. From: Dave Page <[email protected]> Date: Thursday, 14 January 2021 at 10:26 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). - As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this. The search box could be on the Help menu, with results shown in a sub-menu under it. Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default. I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu. What is your reasoning? It is, after all, specifically designed to help people. OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? - As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading. Hmm, OK. Please do let me know your valuable inputs on this. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 Attachments: [application/octet-stream] quick_search_v3.patch (58.1K, 3-quick_search_v3.patch) download | inline diff: diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index 640a05468..672b3e646 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -443,6 +443,19 @@ def register_browser_preferences(self): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', 'open_quick_search', + gettext('Quick Search'), 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 70, 'char': 'f'} + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=fields + ) + self.dynamic_tab_title = self.preference.register( 'tab settings', 'dynamic_tabs', gettext("Dynamic tab size"), 'boolean', False, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js index a62ad0b80..e47b54cb1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js @@ -48,19 +48,19 @@ define('pgadmin.node.package', [ name: 'create_package_on_coll', node: 'coll-package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js index 0523709a0..316f3d70f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js @@ -47,19 +47,19 @@ define('pgadmin.node.synonym', [ name: 'create_synonym_on_coll', node: 'coll-synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js index 57d01b5a0..6fa2613d0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -54,25 +54,25 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_on_coll', node: 'coll-compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onTable', node: 'table', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onPartition', node: 'partition', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'enable_compound_trigger', node: 'compound_trigger', module: this, @@ -88,7 +88,7 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_onView', node: 'view', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index 6a97dd5ba..8163fc721 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -76,12 +76,16 @@ define('pgadmin.node.database', [ name: 'connect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'connect_database', category: 'connect', priority: 4, label: gettext('Connect Database...'), - icon: 'fa fa-link', enable : 'is_not_connected', + icon: 'fa fa-link', enable : 'is_not_connected', data: { + 'data_disabled': gettext('Selected database is already connected.'), + }, },{ name: 'disconnect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'disconnect_database', category: 'drop', priority: 5, label: gettext('Disconnect Database...'), - icon: 'fa fa-unlink', enable : 'is_connected', + icon: 'fa fa-unlink', enable : 'is_connected',data: { + 'data_disabled': gettext('Selected database is already disconnected.'), + }, }]); _.bindAll(this, 'connection_lost'); diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js index 50156ca73..053072e5c 100644 --- a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js +++ b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js @@ -48,7 +48,7 @@ define('pgadmin.node.resource_group', [ name: 'create_resourcegroup_on_server', node: 'server', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, /* Function is used to check the server type and version. * Resource Group only supported in PPAS 9.4 and above. */ @@ -62,12 +62,12 @@ define('pgadmin.node.resource_group', [ name: 'create_resource_group_on_coll', node: 'coll-resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, },{ name: 'create_resource_group', node: 'resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, }, ]); }, diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 13c00c4dc..72da6ca2d 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -100,12 +100,16 @@ define('pgadmin.node.server', [ name: 'reload_configuration', node: 'server', module: this, applies: ['tools', 'context'], callback: 'reload_configuration', category: 'reload', priority: 6, label: gettext('Reload Configuration'), - icon: 'fa fa-redo-alt', enable : 'enable_reload_config', + icon: 'fa fa-redo-alt', enable : 'enable_reload_config',data: { + 'data_disabled': gettext('Please select a server from the browser tree to reload the configuration files.'), + }, },{ name: 'restore_point', node: 'server', module: this, applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'), - icon: 'fa fa-anchor', enable : 'is_applicable', + icon: 'fa fa-anchor', enable : 'is_applicable',data: { + 'data_disabled': gettext('Please select any server from the browser tree to Add Named Restore Point.'), + }, },{ name: 'change_password', node: 'server', module: this, applies: ['object'], callback: 'change_password', @@ -115,12 +119,16 @@ define('pgadmin.node.server', [ name: 'wal_replay_pause', node: 'server', module: this, applies: ['tools', 'context'], callback: 'pause_wal_replay', category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'), - icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled', + icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'), + }, },{ name: 'wal_replay_resume', node: 'server', module: this, applies: ['tools', 'context'], callback: 'resume_wal_replay', category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'), - icon: 'fa fa-play-circle', enable : 'wal_resume_enabled', + icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'), + }, },{ name: 'clear_saved_password', node: 'server', module: this, applies: ['object', 'context'], callback: 'clear_saved_password', @@ -145,6 +153,9 @@ define('pgadmin.node.server', [ } return false; }, + data: { + 'data_disabled': gettext('SSH Tunnel password is not saved for selected server.'), + }, }]); _.bindAll(this, 'connection_lost'); diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index a2b880991..7902f86d9 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -875,6 +875,10 @@ define('pgadmin.browser', [ var $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> SUGGESTED SITES</div>'); + } if (pgAdmin.Browser.MenuCreator( obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories @@ -902,6 +906,7 @@ define('pgadmin.browser', [ } else if(type == 'dialog_help') { window.open(url, 'pgadmin_help'); } + $('#live-search-field').focus(); }, _findTreeChildNode: function(_i, _d, _o) { var loaded = _o.t.wasLoad(_i), @@ -2224,6 +2229,8 @@ define('pgadmin.browser', [ if (pgBrowser.utils.useSpaces == 'True') { pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab'; } - + setTimeout(function(){ + $('#mnu_about').closest('li').before('<li class="dropdown-divider"></li>'); + }, 100); return pgAdmin.Browser; }); diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 3381162d0..a69c7009f 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value), 'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value), 'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), + 'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), }; this.shortcutMethods = { @@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple 'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row + 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; this.bindShortcuts(); } @@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, { return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut); }, + bindOpenQuickSearch: function() { + $('#search_icon').trigger('click'); + }, }); module.exports = pgAdmin.Browser.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index 53d40dcbe..b2f12ff47 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -67,12 +67,17 @@ define([ }, this.menu_items); this.$el = create_submenu.$el; } else { + var data_disabled = null; + if(this.data != undefined && this.data.data_disabled != undefined){ + data_disabled = this.data.data_disabled; + } var url = $('<a></a>', { 'id': this.name, 'href': this.url, 'target': this.target, 'data-toggle': 'pg-menu', 'role': 'menuitem', + 'data-disabled': data_disabled, }).data('pgMenu', { module: this.module || pgAdmin.Browser, cb: this.callback, diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 9c5560ca0..090076fde 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -150,6 +150,7 @@ define('pgadmin.browser.node', [ label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'), data: { 'url': 'drop', + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-trash-alt', enable: _.isFunction(self.canDrop) ? @@ -232,6 +233,7 @@ define('pgadmin.browser.node', [ category: gettext('Scripts'), data: { 'script': stype, + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-pencil-alt', enable: self.check_user_permission, diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js new file mode 100644 index 000000000..5f0120407 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search.js @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, 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); + +// Entry point - Quick search functionality +/*if (document.getElementById('quick-search-component')) { + ReactDOM.render( + <Search onResult={onResultFetch} />, + document.getElementById('quick-search-component') + ); +}*/ diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js new file mode 100644 index 000000000..bf9f8c059 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +// Allow us to render IFrame using React +// Here we will add the event listener on Iframe load event +export class Iframe extends Component { + static get propTypes() { + return { + id: PropTypes.string.isRequired, + srcURL: PropTypes.string.isRequired, + onLoad: PropTypes.func.isRequired, + }; + } + + render () { + const iframeStyle = { + border: '0', + display: 'block', + position:'absolute', + opacity:'0', + }; + const {id, srcURL, onLoad} = this.props; + + return ( + <iframe + id={id} + src={srcURL} + onLoad={onLoad} + width={'20'} + height={'20'} + style={iframeStyle} + /> + ); + } +} diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js new file mode 100644 index 000000000..f3d08aecc --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js @@ -0,0 +1,91 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import gettext from 'sources/gettext'; + + + +// 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; +}; + +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); + } else { + parseLI(_menu, path); + } + } + }); + }; + + // 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); + + mainMenus.forEach((menu) => { + iterItem(menu, getMenuName(menu)); + }); +} diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js new file mode 100644 index 000000000..9808ad007 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/online_help.js @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Iframe} from './iframe_component'; +import url_for from 'sources/url_for'; + +const extractSearchResult = (list) => { + let result = {}; + for (let idx = 0; idx < list.length; idx++) { + let link = list[idx].getElementsByTagName('A'); + // we are not going to display more than first 10 result as per design + if (link.length == 0) { + break; + } + let topicName = link[0].text; + let topicLink = url_for('help.static', { + 'filename': link[0].getAttribute('href'), + }); + result[topicName] = topicLink; + } + return result; +}; + +export function onlineHelpSearch(param, props) { + param = param.split(' ').join('+'); + const setState = props.setState; + const helpURL = url_for('help.static', { + 'filename': 'search.html', + }); + const srcURL = `${helpURL}?q=${param}`; + let isIFrameLoaded = false; + if(document.getElementById('hidden-quick-search-iframe')){ + document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true); + } + + // Below function will be called when the page will be loaded in Iframe + const _iframeLoaded = () => { + if (isIFrameLoaded) { + return false; + } + isIFrameLoaded = true; + let iframe = document.getElementById('hidden-quick-search-iframe'); + let content = (iframe.contentWindow || iframe.contentDocument); + 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); + 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: {}, + })); + ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container')); + isIFrameLoaded = false; + window.clearInterval(pooling); + } + }, 500); + }; + + // Render IFrame + ReactDOM.render( + <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>, + document.getElementById('quick-search-iframe-container'), + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js new file mode 100644 index 000000000..7c94dbd93 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, {useRef,useState, useEffect} from 'react'; +import {useDelayDebounce} from 'sources/custom_hooks'; +import {onlineHelpSearch} from './online_help'; +import {menuSearch} from './menuitems_help'; +import $ from 'jquery'; +import gettext from 'sources/gettext'; + +export function Search() { + const wrapperRef = useRef(null); + const [searchTerm, setSearchTerm] = useState(''); + const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); + let helpLinkTitles = []; + let helpLinks = []; + const [isMenuLoading, setIsMenuLoading] = useState(false); + const [isHelpLoading, setIsHelpLoading] = useState(false); + const [menuSearchResult, setMenuSearchResult] = useState({ + fetched: false, + data: [], + }); + const [helpSearchResult, setHelpSearchResult] = useState({ + fetched: false, + clearedPooling: true, + url: '', + data: [], + }); + + const [showResults, setShowResults] = useState(false); + + const resetSearchState = () => { + setMenuSearchResult(state => ({ + ...state, + fetched: false, + data: [], + })); + setHelpSearchResult(state => ({ + ...state, + fetched: false, + clearedPooling: true, + url: '', + data: {}, + })); + }; + + // Below will be called when any changes has been made to state + useEffect(() => { + helpLinkTitles = Object.keys(helpSearchResult.data); + for(let i = 0; i<helpLinkTitles.length;i++){ + helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>); + } + + if(menuSearchResult.fetched == true){ + setIsMenuLoading(false); + } + + if(helpSearchResult.fetched == true){ + setIsHelpLoading(false); + } + }, [menuSearchResult, helpSearchResult]); + + const initSearch = (param) => { + setIsMenuLoading(true); + setIsHelpLoading(true); + + onlineHelpSearch(param, { + state: helpSearchResult, + setState: setHelpSearchResult, + }); + menuSearch(param, { + state: menuSearchResult, + setState: setMenuSearchResult, + }); + }; + + + // Debounse logic to avoid multiple re-render with each keypress + useDelayDebounce(initSearch, searchTerm, 1000); + + const toggleDropdownMenu = () => { + let pooling = window.pooling; + if(pooling){ + window.clearInterval(pooling); + } + document.getElementsByClassName('live-search-field')[0].value = ''; + setTimeout(function(){ + document.getElementById('live-search-field').focus(); + },100); + resetSearchState(); + setShowResults(!showResults); + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(false); + }; + + const refactorMenuItems = (items) => { + if(items.length > 0){ + let menuItemsHtmlElement = []; + for(let i=0; i < items.length; i++){ + Object.keys(items[i]).map( (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') == true ? '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') == true && 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>); + } + }); + } + $('[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; + if(pooling){ + window.clearInterval(pooling); + } + resetSearchState(); + setSearchTerm(''); + if(value.length >= 3){ + setSearchTerm(value); + setIsMenuLoading(true); + setIsHelpLoading(true); + setIsShowMinLengthMsg(false); + }else{ + setIsMenuLoading(false); + setIsHelpLoading(false); + } + + if(value.length < 3 && value.length > 0){ + setIsShowMinLengthMsg(true); + } + + if(value.length == 0){ + setIsShowMinLengthMsg(false); + } + }; + + const useOutsideAlerter = (ref) => { + useEffect(() => { + /** + * Alert if clicked on outside of element + */ + function handleClickOutside(event) { + if (ref.current && !ref.current.contains(event.target)) { + let input_element = document.getElementById('live-search-field'); + let input_value = input_element.value; + if(input_value && input_value.length > 0){ + toggleDropdownMenu(); + } + return; + } + } + // Bind the event listener + document.addEventListener('mousedown', handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref]); + }; + + useOutsideAlerter(wrapperRef); + + return ( + <div id='quick-search-container' onClick={setSearchTerm}></div>, + <ul id='quick-search-container' ref={wrapperRef} className='test' 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' + aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } /> + </li> + <div style={{marginBottom:0}}> + <div> + + { isShowMinLengthMsg + ? (<div className='pad-12 no-results'> + <span className='fa fa-info-circle'></span> + Please enter minimum 3 characters to search + </div>) + :''} + <div > + + { (menuSearchResult.fetched == true && isMenuLoading == false ) ? + <div> + <div className='menu-groups'> + <span className='fa fa-window-maximize'></span> {gettext('MENU ITEMS')} ({menuSearchResult.data.length}) + </div> + + + {refactorMenuItems(menuSearchResult.data)} + </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')} + + {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + + { (helpSearchResult.fetched == true && isHelpLoading == false) ? + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> {gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ? + <span>(10 of {Object.keys(helpSearchResult.data).length} ) + </span>: + '(' + Object.keys(helpSearchResult.data).length + ')'} + { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''} + { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} <span className='fas fa-external-link-alt' ></span></a> : ''} + </div> + + {Object.keys(helpSearchResult.data).map( (value, index) => { + if(index <= 9) { return <li key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; } + })} + + {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + </div> : ( (isHelpLoading && isMenuLoading == false) ? ( + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> + HELP ARTICLES + {Object.keys(helpSearchResult.data).length > 10 + ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')' + : '(' + Object.keys(helpSearchResult.data).length + ')' + } + { Object.keys(helpSearchResult.data).length > 10 + ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'> + Show all <span className='fas fa-external-link-alt' ></span></a> : '' + } + </div> + <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> + </div>) : '')} + </div> + </div> + </div> + </ul> + </li> + <div id='quick-search-iframe-container' /> + </ul> + ); + +} diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss new file mode 100644 index 000000000..ea90a62d2 --- /dev/null +++ b/web/pgadmin/browser/static/scss/_quick_search.scss @@ -0,0 +1,151 @@ +#myInput { + box-sizing: border-box; + background-position: 14px 12px; + background-repeat: no-repeat; + font-size: 16px; + padding: 14px 20px 12px 45px; + border: none; + border-bottom: 1px solid #ddd; +} + +#myInput:focus {outline: 3px solid #ddd;} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + background-color: $color-bg; + min-width: 376px; + overflow: auto; + z-index: 1; +} + +.custom-dropdown-content a { + color: $dropdown-link-color; + padding: 6px 12px 6px 16px; + text-decoration: none; + display: block; + cursor:pointer; +} + +.custom-dropdown-content a:hover { + color: $black; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;} + +.search_icon{ + color: $white; + cursor: pointer; + padding-right: 8px; +} +.hidden { display:none; } + +.visible { display:block; } + +.menu-groups, .help-groups{ + background-color: $color-gray-light; + padding: 6px; + font-size: 12px; + font-weight: 600; +} + +.menu-groups .fa, .fas{ + font-weight:normal !important; +} + +.help-groups .fa, .fas{ + font-weight:600 !important; +} + +.pad-12{ + padding:12px; +} + +.no-results{ + font-size: 14px; + color: #697986; + text-align: center; +} + +.no-padding{ + padding:0 !important; +} + +.menu-groups-a{ + display:flex !important; + flex-direction:column; + padding: 6px; + color: $dropdown-link-color; + padding: 6px 16px; +} + +.menu-groups-a span{ + font-size: 0.9em; + font-weight: 100; + color: $quick-search-span-text; +} + +#myDropdown a:hover span{ + color: $color-danger-fg !important; +} + +.search-icon{ + background: $loader-icon-small center center no-repeat; + margin: auto !important; + height: 22px !important; + width: 130px !important; + background-position: left !important; + font-size: 14px; + color: $dropdown-link-color; + padding-left: 30px; +} + +.help_loading_icon{ + height: 16px; +} +#myDropdown ul { + list-style: none; +} + +.border-right-search-icon{ + border-right: 2px solid #fff; +} + +.help_submenu{ + left: 100%; + width: 20rem; + top:-0.4rem; +} + +.help_menu{ + min-width:300px; +} + +.dropdown-item-input{ + padding:4px; +} + +.menu-groups-a span:focus{ + color:$color-primary-fg; +} + +.dropdown-item:hover, .dropdown-item:focus span{ + color: $color-danger-fg !important; +} + +.quick-search-tooltip{ + position: absolute; + right: 10px; + margin-top: -2.1em; + font-size: 16px; + cursor:pointer; + color: $quick-search-info-icon; +} + +#myDropdown .dropdown-divider{ + height: auto; + border-top: 0; +} diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index c571df246..c1387dad5 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -133,7 +133,7 @@ window.onload = function(e){ <li id="mnu_help" class="nav-item active dropdown d-none"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Help') }} <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"></ul> + <ul class="dropdown-menu help_menu" role="menu"></ul> </li> </ul> {% if config.SERVER_MODE %} diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 zcmc(iX;4#H9>pJdFE7h`I{IF)0|5<6L}(j=N}5%L009EB2nYfyF)E0PvIqo$u!IC; z4PgyY5|S9AEh38G)(9eq4TbH7_UHg@yWrlIJ$6smIADL7s^P;_O;ykRc<bJ}b<Y2s zU)AOL`#QVCGXW;>9soXl`UC*LwQJXkii*0rx|*7rI2=x7WaRkx_~XZqFJ8R3c=2Kg zf@aSAv8+BJ8+^hyay>(QR@t*blbKzsf0}bscEqRc5Hd3o(-N5RyW=zWB*zQw6Zh>* z2CROCDAbu#D`)S|J_<lj7Yz9)#_Og>o(lL9Yn3l*+8RdiRD_>iNz$#_IAzCna&Wl5 zSF_(rRCDD!wi#i8oAm&jYtn2_@VB%2-H*G%bN#|(6R6N?wM)3u`PiGzwuX7qmTgyF zpE)h0kuoxQ9?=kW7Y!=R@DmhU9)vwT<ZMc0Y;&y4jY1%TT3z!|H=R-GXDHPiKcVWh zY+!etO=DI2rIs8{iFWtPv(Lu|O3u|$F3Sbq;+xF{gTX$#T%m?MUUZy&ug3$=zXgXj zrxrf}reg*D3HB~8JyLgl$UCyV?EQ`@OKjW@tGrvh6ZqPD#+m=rK0T{FT01>*EZWzJ zrt+=2tqFts72yIp?|gvdLhs8Hfku^Z(){gmN%Y=K#<L1VKWYjwV^JDyeS;Y$p1xw* z#3VzfAV>P|%fkvg<hUP3U1Q=Hdgg~ik+2zyAc79kpuA<f*-~l+ZBH3*S2jBrEOF0w zrxe9#Vx$SxnL0JE4WeeXY1)ppOIy3@Vvexu&oeIa&QvoD`jBE#Gd7rT{j&OMLz1Wu zOEj;)PR^=mxjCG0NOUJb&U;ui6*-`3&wmcQ>Uj~HfIp3CuXqCtYGtJ#me+n+-LmP( z*XNuk%!aH8bIE@_Bj46>M*dSro|7<6vZ7WUHh5YQzN$>IJFqCb|CT!wj~R2C2%=q{ zpt8rzY$aw?W?=Ustv{jo?Ow@<k6~~d?F>ZRkLe<)NItY>Cyhle*wR59dTdF6(@{5^ zAQBOB*hNtc3bkY-8{Cm$nFS@elbTtSqrt7MB{h_4y+~`!mVa}?c&N>&?P}GqdMuhQ z&@TD5Czd((DcG_Su~dKKV)Pj$-qi1WHM8_vc^O4?^!oY|tmK~i!{fjd&@_1E(T~r7 z_REZy&hMT^ySJB3W7l<L=l9ZMvC<Gz>$4YhR`M(J7S5S~+4Q&3HPa)z%zPpisOp$^ zTEe99ig2$5_qFr!$;<oK+H}=wcaT3=%Nm!;Kw7MHnU5paWS{tI1+DOU?!7xefZ57L ze_iPrUrRQct0FSCtTFLtg*<#jo}Z3{E?T{skj>7A6CJ}PJmRhli>w?LC}Y`#HLGy6 zMU4<C6_PR!wGq`HQyoWJb;nj8>EhL~dKCN5Ut;U2jd*83ShB<kA1Y@1U)Ar;N|HhS znIkwkT(&i5XhkI;xwmC%DvPhGNIi?aY<|8rajSt<ap(2E-#qSPQxAp@jIY@-@>Niu zcJB0l9>1Modc?-oM<<M{t-|U0{*W+=Ct2ZY_02y-De{7vW<f^HJQhd1l&4)Gw2oOS zm46KASlsKI@J$sA#$$|7D5QMbewIaFv4fXyNbL5Ac~kS&g^#5XHaYBvNxbF3Y2L*6 ztrn?JmgOFAo1lh99BEb^pp>R<Z&2wFwWd*z2wF6&nmW9}nyMfWMO`hc&zkr2AeBP3 zj75NZQ8-VthLviI^j@e=FN6wxR@1uCRv<b;Y<3t(dr<e}N%b}FQtKxHi9xU2C!#0Z zO2<#(;s&964KtWfkQVi``vIFT7kbT~d;ITb0T9+U1AwIgET*ciil)~4gl;xgoy5M! z-UJHerGNh_`lO!vA)%ly=~<}ykhlnQnoP$oqido+`qK(cOpmt^pbhf`n-FQaIK5ix zq@=#Sl2Y&s<pe8B!1!YA78W7dA?2Xu9v7QHc?}NN)sx(o6iZ#|kHX64nijZG(yB1J zfMQm;1rb5O!-+1Pov;csFu7z>4?<d6>}3g}UJ%@K);kriq>)e*rh%hdqM)5Q)*+O8 zXm;SEbs@koiYS!9YXIclSg+5m_s~yrW#kKMdiRszg(gCP5HPmP7L)vCf8@fxUh6qY z@Z#TmkjzAZX{rwE+q|K~F2v5{_@vt%>yT_a#fF03SFt{0RX<yi^Bg0BS3UHmG;U4d z`2QlHs<l7ezUo)s<V^9ZccYv>vDAiaY~K9CgS1O>frXgAjBCS}mEd4mIWZ$=ovd5| zR?GRdU}d6+Q`+JRW)|=v7$)X<at#L3(d9WVd8CstDNPh>Nkn3yE`!nAiSCvOB1jKT zG<1aK3s<0b0m==egTD#8i(<nFTpHvxfx|aIng5yR81z6E<naz8-Ow^p@sCs8mz=%h zO$v$X0NS?ofjnp~62AE}^z%gY8Nsqj=NwUqyj+o6s$@kK@d+U4Vp-^_G32vzv@8nI z01{`FL$DXQL%WB*9R<xn7$ya31flsbiVh+-0m=YeB_ocaW;YRxI51d(jP?N!ane91 z9~^yzJ;S;OWRKC8PrrXYkZCaruNYE>Of=1pGDTOCho0XpIOMQ&P87cVKY1W=C6kIg z9cH=@a&zbm2+`|{(_?YC9fdm?1TY~-pwlBn?>=(~1pDKbco6jloP;0-cqRiwV1A_S zEyV0Dj8Pwy!nekzaN>{)7rgZ&_QLxK{~1yRe865^<m)Ax^m58MY|zev&92(G7#vQU zn~8r)5oUrwM9`}05|I<Nx*n}jlvg&C9_310Dd4OT2txd91Z*_U8bRtrNaq+nGd{E# zVGckZFpr^;mv}%%T{jHtz<a=^%;mPXVY7SR`@6_Uw@(0*>yx>}+a!ECd>#MMwddow z@CU{l+Rt$xuXuf}?ga{3IAr?Raql^c@a%sI0U5m}HvJ5O1#I%_MMPt#BH>OqUZ{-k zt>4Xzz=%jT*FVW(uYkWyx}9Gw$HdN*qU?Bit#ji(Wi7p-u|_8?h^%szIS^s^fNM}b zgGy>|=cbEufpguY5_6w~&ZLv=Bo06UF9EYIY;Er-1VK)SyF&!|J{axiE1z^(hXwVq zsFS=K-#zC}CcOs^8W{KAt+kK)jYDgDYbCXv{{<mZ_TMxh0{w%6lzzG*pm+Dj4XaZ5 zoJwkk5)~fyUmzYbwMERR3j)XePHj^2P!5GK`~^RXuEz>rwsgqtIU3<910$CJi)s?? z_t8k{>7*0~4l~LLF7$WXT5OSq5QCTbP_l!SN|{R}3D&eWA8~0ltWh1IL+ZBX4rRSt zWF6Om3WDMu4xK^1(BF`2cL}rUCzhHAB`@j5&R-yk_l*t;mPGY|u2^o|myvcOdrg0W z%=lX;f^Vkqfp?u7*4qQq%A3Mpf!xspWBSKS@O%r*TSM}?dl(@*%{0Jm_8;(h{R__M Bt<?Yk literal 0 HcmV?d00001 diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 9163f0d6c..b99dcbab4 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -26,4 +26,16 @@ export function usePrevious(value) { ref.current = value; }); return ref.current; -} \ No newline at end of file +} + +export function useDelayDebounce(callback, args, delay) { + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (args) { + callback(args); + } + }, delay); + return () => clearTimeout(delayDebounceFn); + }, [args]); +} + diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss index fadd71b7b..7a6dfac26 100644 --- a/web/pgadmin/static/scss/_pgadmin.style.scss +++ b/web/pgadmin/static/scss/_pgadmin.style.scss @@ -1123,3 +1123,10 @@ select:-webkit-autofill:focus { .pull-left{ float:left } + +.menu-groups-a:hover span{ + color: $white !important; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;} + diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss index 9d913788d..7b42fe38a 100644 --- a/web/pgadmin/static/scss/resources/_default.variables.scss +++ b/web/pgadmin/static/scss/resources/_default.variables.scss @@ -4,6 +4,9 @@ $enable-flex: true; $white: #fff; $black: #000; +$span-text-color: #6B6B6B !default; +$span-text-color-hover: #6B6B6B !default; + $color-bg: $white !default; $color-fg: #222222 !default; @@ -349,3 +352,7 @@ $grid-hover-fg-color: $color-fg !default; $btn-copied-color-fg: $active-color !default; +$quick-search-a-text-color: $black !default; +$quick-search-span-text: $span-text-color !default; +$quick-search-span-text-hover: $span-text-color-hover !default; +$quick-search-info-icon: #646C82 !default; diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss index 129b9a214..95bc2bc3a 100644 --- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss @@ -121,3 +121,8 @@ $color-success-hover-fg: $color-fg; $datagrid-selected-color: $color-primary-fg; $select2-placeholder: #999; + +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $white !default; +$quick-search-a-text-color: $white !default; +$quick-search-info-icon: #8A8A8A !default; diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss index b895a1223..107a3627b 100644 --- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss @@ -1,4 +1,5 @@ $white: #FFFFFF; +$black: #000; $color-bg: #010B15; $color-fg: $white; @@ -202,4 +203,7 @@ $grid-hover-fg-color: #010B15; $btn-copied-color-fg: #010B15; - +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $black !default; +$quick-search-a-text-color: $black !default; +$quick-search-info-icon: #8A8A8A !default; diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 1552166d2..ef9116958 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -582,6 +582,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Backup of global objects.'), + }, }, { name: 'backup_server', module: this, @@ -591,6 +594,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_global_ctx', module: this, @@ -601,6 +607,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }, { name: 'backup_server_ctx', module: this, @@ -611,6 +620,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_object', module: this, @@ -622,6 +634,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }]; for (var idx = 0; idx < menuUtils.backupSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index a7d056683..914695abf 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -113,6 +113,9 @@ define('pgadmin.datagrid', [ priority: 1, label: gettext('Query Tool'), icon: 'pg-font-icon icon-query-tool', + data:{ + 'data_disabled': gettext('Please select a database from the browser tree to access Query Tool.'), + }, }]; // Create context menu diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index be420826d..c7c7c459c 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -177,6 +177,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.supportedNodes ), + data: { + 'data_disabled': gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'), + }, }]; // Add supported menus into the menus list diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index bef95d114..d225183ce 100644 --- a/web/pgadmin/tools/import_export/static/js/import_export.js +++ b/web/pgadmin/tools/import_export/static/js/import_export.js @@ -412,6 +412,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, ['table'] ), + data: { + 'data_disabled': gettext('Please select any table from the browser tree to Import/Export data.'), + }, }]); }, diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index a8f331094..cf850c76d 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -184,6 +184,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any database from the browser tree to do Maintenance.'), + }, }); } pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index e365b5a76..6c9ff8fda 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -398,6 +398,9 @@ define('tools.restore', [ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any schema/table from the browser tree to Restore data.'), + }, }]; for (var idx = 0; idx < menuUtils.restoreSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/search_objects/static/js/search_objects.js b/web/pgadmin/tools/search_objects/static/js/search_objects.js index fcd52fd7f..ab898bf38 100644 --- a/web/pgadmin/tools/search_objects/static/js/search_objects.js +++ b/web/pgadmin/tools/search_objects/static/js/search_objects.js @@ -36,6 +36,9 @@ define([ enable: this.search_objects_enabled, priority: 1, label: gettext('Search Objects...'), + data: { + 'data_disabled': gettext('Please select a database from the browser tree to search the database objects.'), + }, }, { name: 'search_objects', module: this, diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js new file mode 100644 index 000000000..d7f59563f --- /dev/null +++ b/web/regression/javascript/quick_search/quick_search_spec.js @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { Search } from 'browser/quick_search/trigger_search'; + +let container; + +describe('quick search test cases', function () { + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + ReactDOM.render(<Search />, container); + }); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should have rendered quick-search-container', () => { + expect(container.firstChild.id).toEqual('quick-search-container'); + }); + + it('should have 2 childs in quick-search-container', () => { + expect(container.firstChild.childNodes.length).toEqual(2); + }); + + it('element should be html element', () => { + let inputElement = document.getElementById('live-search-field'); + expect(inputElement instanceof HTMLElement).toBeTruthy(); + }); + +}); diff --git a/web/webpack.config.js b/web/webpack.config.js index 96ef708ab..e6655512a 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -421,6 +421,7 @@ module.exports = [{ use: { loader: 'imports-loader?' + 'pgadmin.dashboard' + + ',pgadmin.browser.quick_search' + ',pgadmin.tools.user_management' + ',pgadmin.browser.object_statistics' + ',pgadmin.browser.dependencies' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 6fc26021e..bddcda9b8 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -181,6 +181,7 @@ var webpackShimConfig = { 'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'), 'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'), 'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'), + 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), [image/png] image001.png (68.4K, 4-image001.png) download | view image [image/png] image002.png (68.4K, 5-image002.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-20 11:06 Pramod Ahire <[email protected]> parent: Pramod Ahire <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Pramod Ahire @ 2021-01-20 11:06 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; Dave Page <[email protected]>; +Cc: pgadmin-hackers Hi Team, Sorry for the inconvenience, kindly please discard previous email. Please review patch attached to this email. Changes as below : 1. Added complete quick search menu to help dropdown 2. Added message to each disabled menu item to describe why it can be disabled 3. Info icon design to menu items results if disabled 4. Standard/dark/high contrast theme supports NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Pramod Ahire <[email protected]> Date: Wednesday, 20 January 2021 at 4:19 PM To: Akshay Joshi <[email protected]>, Dave Page <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Team, As per inputs from you all, made changes & attached patch with this email. Kindly please have a review & do let me know if missed anything. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Friday, 15 January 2021 at 3:34 PM To: Dave Page <[email protected]> Cc: Pramod Ahire <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote: Hi Dave On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: Hi On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote: Hi Dave, Thank you for comments ! I had described points below. Please do let me know if anything missing. From: Dave Page <[email protected]> Date: Thursday, 14 January 2021 at 10:26 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). - As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this. The search box could be on the Help menu, with results shown in a sub-menu under it. Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default. I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu. What is your reasoning? It is, after all, specifically designed to help people. OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? - As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading. Hmm, OK. Please do let me know your valuable inputs on this. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 Attachments: [application/octet-stream] quick_search_v3.patch (58.1K, 3-quick_search_v3.patch) download | inline diff: diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index 640a05468..672b3e646 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -443,6 +443,19 @@ def register_browser_preferences(self): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', 'open_quick_search', + gettext('Quick Search'), 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 70, 'char': 'f'} + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=fields + ) + self.dynamic_tab_title = self.preference.register( 'tab settings', 'dynamic_tabs', gettext("Dynamic tab size"), 'boolean', False, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js index a62ad0b80..e47b54cb1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js @@ -48,19 +48,19 @@ define('pgadmin.node.package', [ name: 'create_package_on_coll', node: 'coll-package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js index 0523709a0..316f3d70f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js @@ -47,19 +47,19 @@ define('pgadmin.node.synonym', [ name: 'create_synonym_on_coll', node: 'coll-synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js index 57d01b5a0..6fa2613d0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -54,25 +54,25 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_on_coll', node: 'coll-compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onTable', node: 'table', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onPartition', node: 'partition', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'enable_compound_trigger', node: 'compound_trigger', module: this, @@ -88,7 +88,7 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_onView', node: 'view', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index 6a97dd5ba..8163fc721 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -76,12 +76,16 @@ define('pgadmin.node.database', [ name: 'connect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'connect_database', category: 'connect', priority: 4, label: gettext('Connect Database...'), - icon: 'fa fa-link', enable : 'is_not_connected', + icon: 'fa fa-link', enable : 'is_not_connected', data: { + 'data_disabled': gettext('Selected database is already connected.'), + }, },{ name: 'disconnect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'disconnect_database', category: 'drop', priority: 5, label: gettext('Disconnect Database...'), - icon: 'fa fa-unlink', enable : 'is_connected', + icon: 'fa fa-unlink', enable : 'is_connected',data: { + 'data_disabled': gettext('Selected database is already disconnected.'), + }, }]); _.bindAll(this, 'connection_lost'); diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js index 50156ca73..053072e5c 100644 --- a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js +++ b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js @@ -48,7 +48,7 @@ define('pgadmin.node.resource_group', [ name: 'create_resourcegroup_on_server', node: 'server', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, /* Function is used to check the server type and version. * Resource Group only supported in PPAS 9.4 and above. */ @@ -62,12 +62,12 @@ define('pgadmin.node.resource_group', [ name: 'create_resource_group_on_coll', node: 'coll-resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, },{ name: 'create_resource_group', node: 'resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, }, ]); }, diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 13c00c4dc..72da6ca2d 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -100,12 +100,16 @@ define('pgadmin.node.server', [ name: 'reload_configuration', node: 'server', module: this, applies: ['tools', 'context'], callback: 'reload_configuration', category: 'reload', priority: 6, label: gettext('Reload Configuration'), - icon: 'fa fa-redo-alt', enable : 'enable_reload_config', + icon: 'fa fa-redo-alt', enable : 'enable_reload_config',data: { + 'data_disabled': gettext('Please select a server from the browser tree to reload the configuration files.'), + }, },{ name: 'restore_point', node: 'server', module: this, applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'), - icon: 'fa fa-anchor', enable : 'is_applicable', + icon: 'fa fa-anchor', enable : 'is_applicable',data: { + 'data_disabled': gettext('Please select any server from the browser tree to Add Named Restore Point.'), + }, },{ name: 'change_password', node: 'server', module: this, applies: ['object'], callback: 'change_password', @@ -115,12 +119,16 @@ define('pgadmin.node.server', [ name: 'wal_replay_pause', node: 'server', module: this, applies: ['tools', 'context'], callback: 'pause_wal_replay', category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'), - icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled', + icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'), + }, },{ name: 'wal_replay_resume', node: 'server', module: this, applies: ['tools', 'context'], callback: 'resume_wal_replay', category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'), - icon: 'fa fa-play-circle', enable : 'wal_resume_enabled', + icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'), + }, },{ name: 'clear_saved_password', node: 'server', module: this, applies: ['object', 'context'], callback: 'clear_saved_password', @@ -145,6 +153,9 @@ define('pgadmin.node.server', [ } return false; }, + data: { + 'data_disabled': gettext('SSH Tunnel password is not saved for selected server.'), + }, }]); _.bindAll(this, 'connection_lost'); diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index a2b880991..7902f86d9 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -875,6 +875,10 @@ define('pgadmin.browser', [ var $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> SUGGESTED SITES</div>'); + } if (pgAdmin.Browser.MenuCreator( obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories @@ -902,6 +906,7 @@ define('pgadmin.browser', [ } else if(type == 'dialog_help') { window.open(url, 'pgadmin_help'); } + $('#live-search-field').focus(); }, _findTreeChildNode: function(_i, _d, _o) { var loaded = _o.t.wasLoad(_i), @@ -2224,6 +2229,8 @@ define('pgadmin.browser', [ if (pgBrowser.utils.useSpaces == 'True') { pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab'; } - + setTimeout(function(){ + $('#mnu_about').closest('li').before('<li class="dropdown-divider"></li>'); + }, 100); return pgAdmin.Browser; }); diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 3381162d0..a69c7009f 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value), 'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value), 'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), + 'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), }; this.shortcutMethods = { @@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple 'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row + 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; this.bindShortcuts(); } @@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, { return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut); }, + bindOpenQuickSearch: function() { + $('#search_icon').trigger('click'); + }, }); module.exports = pgAdmin.Browser.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index 53d40dcbe..b2f12ff47 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -67,12 +67,17 @@ define([ }, this.menu_items); this.$el = create_submenu.$el; } else { + var data_disabled = null; + if(this.data != undefined && this.data.data_disabled != undefined){ + data_disabled = this.data.data_disabled; + } var url = $('<a></a>', { 'id': this.name, 'href': this.url, 'target': this.target, 'data-toggle': 'pg-menu', 'role': 'menuitem', + 'data-disabled': data_disabled, }).data('pgMenu', { module: this.module || pgAdmin.Browser, cb: this.callback, diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 9c5560ca0..090076fde 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -150,6 +150,7 @@ define('pgadmin.browser.node', [ label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'), data: { 'url': 'drop', + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-trash-alt', enable: _.isFunction(self.canDrop) ? @@ -232,6 +233,7 @@ define('pgadmin.browser.node', [ category: gettext('Scripts'), data: { 'script': stype, + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-pencil-alt', enable: self.check_user_permission, diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js new file mode 100644 index 000000000..5f0120407 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search.js @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, 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); + +// Entry point - Quick search functionality +/*if (document.getElementById('quick-search-component')) { + ReactDOM.render( + <Search onResult={onResultFetch} />, + document.getElementById('quick-search-component') + ); +}*/ diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js new file mode 100644 index 000000000..bf9f8c059 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +// Allow us to render IFrame using React +// Here we will add the event listener on Iframe load event +export class Iframe extends Component { + static get propTypes() { + return { + id: PropTypes.string.isRequired, + srcURL: PropTypes.string.isRequired, + onLoad: PropTypes.func.isRequired, + }; + } + + render () { + const iframeStyle = { + border: '0', + display: 'block', + position:'absolute', + opacity:'0', + }; + const {id, srcURL, onLoad} = this.props; + + return ( + <iframe + id={id} + src={srcURL} + onLoad={onLoad} + width={'20'} + height={'20'} + style={iframeStyle} + /> + ); + } +} diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js new file mode 100644 index 000000000..f3d08aecc --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js @@ -0,0 +1,91 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import gettext from 'sources/gettext'; + + + +// 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; +}; + +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); + } else { + parseLI(_menu, path); + } + } + }); + }; + + // 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); + + mainMenus.forEach((menu) => { + iterItem(menu, getMenuName(menu)); + }); +} diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js new file mode 100644 index 000000000..9808ad007 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/online_help.js @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Iframe} from './iframe_component'; +import url_for from 'sources/url_for'; + +const extractSearchResult = (list) => { + let result = {}; + for (let idx = 0; idx < list.length; idx++) { + let link = list[idx].getElementsByTagName('A'); + // we are not going to display more than first 10 result as per design + if (link.length == 0) { + break; + } + let topicName = link[0].text; + let topicLink = url_for('help.static', { + 'filename': link[0].getAttribute('href'), + }); + result[topicName] = topicLink; + } + return result; +}; + +export function onlineHelpSearch(param, props) { + param = param.split(' ').join('+'); + const setState = props.setState; + const helpURL = url_for('help.static', { + 'filename': 'search.html', + }); + const srcURL = `${helpURL}?q=${param}`; + let isIFrameLoaded = false; + if(document.getElementById('hidden-quick-search-iframe')){ + document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true); + } + + // Below function will be called when the page will be loaded in Iframe + const _iframeLoaded = () => { + if (isIFrameLoaded) { + return false; + } + isIFrameLoaded = true; + let iframe = document.getElementById('hidden-quick-search-iframe'); + let content = (iframe.contentWindow || iframe.contentDocument); + 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); + 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: {}, + })); + ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container')); + isIFrameLoaded = false; + window.clearInterval(pooling); + } + }, 500); + }; + + // Render IFrame + ReactDOM.render( + <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>, + document.getElementById('quick-search-iframe-container'), + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js new file mode 100644 index 000000000..7c94dbd93 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, {useRef,useState, useEffect} from 'react'; +import {useDelayDebounce} from 'sources/custom_hooks'; +import {onlineHelpSearch} from './online_help'; +import {menuSearch} from './menuitems_help'; +import $ from 'jquery'; +import gettext from 'sources/gettext'; + +export function Search() { + const wrapperRef = useRef(null); + const [searchTerm, setSearchTerm] = useState(''); + const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); + let helpLinkTitles = []; + let helpLinks = []; + const [isMenuLoading, setIsMenuLoading] = useState(false); + const [isHelpLoading, setIsHelpLoading] = useState(false); + const [menuSearchResult, setMenuSearchResult] = useState({ + fetched: false, + data: [], + }); + const [helpSearchResult, setHelpSearchResult] = useState({ + fetched: false, + clearedPooling: true, + url: '', + data: [], + }); + + const [showResults, setShowResults] = useState(false); + + const resetSearchState = () => { + setMenuSearchResult(state => ({ + ...state, + fetched: false, + data: [], + })); + setHelpSearchResult(state => ({ + ...state, + fetched: false, + clearedPooling: true, + url: '', + data: {}, + })); + }; + + // Below will be called when any changes has been made to state + useEffect(() => { + helpLinkTitles = Object.keys(helpSearchResult.data); + for(let i = 0; i<helpLinkTitles.length;i++){ + helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>); + } + + if(menuSearchResult.fetched == true){ + setIsMenuLoading(false); + } + + if(helpSearchResult.fetched == true){ + setIsHelpLoading(false); + } + }, [menuSearchResult, helpSearchResult]); + + const initSearch = (param) => { + setIsMenuLoading(true); + setIsHelpLoading(true); + + onlineHelpSearch(param, { + state: helpSearchResult, + setState: setHelpSearchResult, + }); + menuSearch(param, { + state: menuSearchResult, + setState: setMenuSearchResult, + }); + }; + + + // Debounse logic to avoid multiple re-render with each keypress + useDelayDebounce(initSearch, searchTerm, 1000); + + const toggleDropdownMenu = () => { + let pooling = window.pooling; + if(pooling){ + window.clearInterval(pooling); + } + document.getElementsByClassName('live-search-field')[0].value = ''; + setTimeout(function(){ + document.getElementById('live-search-field').focus(); + },100); + resetSearchState(); + setShowResults(!showResults); + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(false); + }; + + const refactorMenuItems = (items) => { + if(items.length > 0){ + let menuItemsHtmlElement = []; + for(let i=0; i < items.length; i++){ + Object.keys(items[i]).map( (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') == true ? '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') == true && 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>); + } + }); + } + $('[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; + if(pooling){ + window.clearInterval(pooling); + } + resetSearchState(); + setSearchTerm(''); + if(value.length >= 3){ + setSearchTerm(value); + setIsMenuLoading(true); + setIsHelpLoading(true); + setIsShowMinLengthMsg(false); + }else{ + setIsMenuLoading(false); + setIsHelpLoading(false); + } + + if(value.length < 3 && value.length > 0){ + setIsShowMinLengthMsg(true); + } + + if(value.length == 0){ + setIsShowMinLengthMsg(false); + } + }; + + const useOutsideAlerter = (ref) => { + useEffect(() => { + /** + * Alert if clicked on outside of element + */ + function handleClickOutside(event) { + if (ref.current && !ref.current.contains(event.target)) { + let input_element = document.getElementById('live-search-field'); + let input_value = input_element.value; + if(input_value && input_value.length > 0){ + toggleDropdownMenu(); + } + return; + } + } + // Bind the event listener + document.addEventListener('mousedown', handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref]); + }; + + useOutsideAlerter(wrapperRef); + + return ( + <div id='quick-search-container' onClick={setSearchTerm}></div>, + <ul id='quick-search-container' ref={wrapperRef} className='test' 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' + aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } /> + </li> + <div style={{marginBottom:0}}> + <div> + + { isShowMinLengthMsg + ? (<div className='pad-12 no-results'> + <span className='fa fa-info-circle'></span> + Please enter minimum 3 characters to search + </div>) + :''} + <div > + + { (menuSearchResult.fetched == true && isMenuLoading == false ) ? + <div> + <div className='menu-groups'> + <span className='fa fa-window-maximize'></span> {gettext('MENU ITEMS')} ({menuSearchResult.data.length}) + </div> + + + {refactorMenuItems(menuSearchResult.data)} + </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')} + + {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + + { (helpSearchResult.fetched == true && isHelpLoading == false) ? + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> {gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ? + <span>(10 of {Object.keys(helpSearchResult.data).length} ) + </span>: + '(' + Object.keys(helpSearchResult.data).length + ')'} + { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''} + { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} <span className='fas fa-external-link-alt' ></span></a> : ''} + </div> + + {Object.keys(helpSearchResult.data).map( (value, index) => { + if(index <= 9) { return <li key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; } + })} + + {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + </div> : ( (isHelpLoading && isMenuLoading == false) ? ( + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> + HELP ARTICLES + {Object.keys(helpSearchResult.data).length > 10 + ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')' + : '(' + Object.keys(helpSearchResult.data).length + ')' + } + { Object.keys(helpSearchResult.data).length > 10 + ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'> + Show all <span className='fas fa-external-link-alt' ></span></a> : '' + } + </div> + <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> + </div>) : '')} + </div> + </div> + </div> + </ul> + </li> + <div id='quick-search-iframe-container' /> + </ul> + ); + +} diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss new file mode 100644 index 000000000..ea90a62d2 --- /dev/null +++ b/web/pgadmin/browser/static/scss/_quick_search.scss @@ -0,0 +1,151 @@ +#myInput { + box-sizing: border-box; + background-position: 14px 12px; + background-repeat: no-repeat; + font-size: 16px; + padding: 14px 20px 12px 45px; + border: none; + border-bottom: 1px solid #ddd; +} + +#myInput:focus {outline: 3px solid #ddd;} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + background-color: $color-bg; + min-width: 376px; + overflow: auto; + z-index: 1; +} + +.custom-dropdown-content a { + color: $dropdown-link-color; + padding: 6px 12px 6px 16px; + text-decoration: none; + display: block; + cursor:pointer; +} + +.custom-dropdown-content a:hover { + color: $black; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;} + +.search_icon{ + color: $white; + cursor: pointer; + padding-right: 8px; +} +.hidden { display:none; } + +.visible { display:block; } + +.menu-groups, .help-groups{ + background-color: $color-gray-light; + padding: 6px; + font-size: 12px; + font-weight: 600; +} + +.menu-groups .fa, .fas{ + font-weight:normal !important; +} + +.help-groups .fa, .fas{ + font-weight:600 !important; +} + +.pad-12{ + padding:12px; +} + +.no-results{ + font-size: 14px; + color: #697986; + text-align: center; +} + +.no-padding{ + padding:0 !important; +} + +.menu-groups-a{ + display:flex !important; + flex-direction:column; + padding: 6px; + color: $dropdown-link-color; + padding: 6px 16px; +} + +.menu-groups-a span{ + font-size: 0.9em; + font-weight: 100; + color: $quick-search-span-text; +} + +#myDropdown a:hover span{ + color: $color-danger-fg !important; +} + +.search-icon{ + background: $loader-icon-small center center no-repeat; + margin: auto !important; + height: 22px !important; + width: 130px !important; + background-position: left !important; + font-size: 14px; + color: $dropdown-link-color; + padding-left: 30px; +} + +.help_loading_icon{ + height: 16px; +} +#myDropdown ul { + list-style: none; +} + +.border-right-search-icon{ + border-right: 2px solid #fff; +} + +.help_submenu{ + left: 100%; + width: 20rem; + top:-0.4rem; +} + +.help_menu{ + min-width:300px; +} + +.dropdown-item-input{ + padding:4px; +} + +.menu-groups-a span:focus{ + color:$color-primary-fg; +} + +.dropdown-item:hover, .dropdown-item:focus span{ + color: $color-danger-fg !important; +} + +.quick-search-tooltip{ + position: absolute; + right: 10px; + margin-top: -2.1em; + font-size: 16px; + cursor:pointer; + color: $quick-search-info-icon; +} + +#myDropdown .dropdown-divider{ + height: auto; + border-top: 0; +} diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index c571df246..c1387dad5 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -133,7 +133,7 @@ window.onload = function(e){ <li id="mnu_help" class="nav-item active dropdown d-none"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Help') }} <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"></ul> + <ul class="dropdown-menu help_menu" role="menu"></ul> </li> </ul> {% if config.SERVER_MODE %} diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 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 Bt<?Yk literal 0 HcmV?d00001 diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 9163f0d6c..b99dcbab4 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -26,4 +26,16 @@ export function usePrevious(value) { ref.current = value; }); return ref.current; -} \ No newline at end of file +} + +export function useDelayDebounce(callback, args, delay) { + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (args) { + callback(args); + } + }, delay); + return () => clearTimeout(delayDebounceFn); + }, [args]); +} + diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss index fadd71b7b..7a6dfac26 100644 --- a/web/pgadmin/static/scss/_pgadmin.style.scss +++ b/web/pgadmin/static/scss/_pgadmin.style.scss @@ -1123,3 +1123,10 @@ select:-webkit-autofill:focus { .pull-left{ float:left } + +.menu-groups-a:hover span{ + color: $white !important; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;} + diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss index 9d913788d..7b42fe38a 100644 --- a/web/pgadmin/static/scss/resources/_default.variables.scss +++ b/web/pgadmin/static/scss/resources/_default.variables.scss @@ -4,6 +4,9 @@ $enable-flex: true; $white: #fff; $black: #000; +$span-text-color: #6B6B6B !default; +$span-text-color-hover: #6B6B6B !default; + $color-bg: $white !default; $color-fg: #222222 !default; @@ -349,3 +352,7 @@ $grid-hover-fg-color: $color-fg !default; $btn-copied-color-fg: $active-color !default; +$quick-search-a-text-color: $black !default; +$quick-search-span-text: $span-text-color !default; +$quick-search-span-text-hover: $span-text-color-hover !default; +$quick-search-info-icon: #646C82 !default; diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss index 129b9a214..95bc2bc3a 100644 --- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss @@ -121,3 +121,8 @@ $color-success-hover-fg: $color-fg; $datagrid-selected-color: $color-primary-fg; $select2-placeholder: #999; + +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $white !default; +$quick-search-a-text-color: $white !default; +$quick-search-info-icon: #8A8A8A !default; diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss index b895a1223..107a3627b 100644 --- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss @@ -1,4 +1,5 @@ $white: #FFFFFF; +$black: #000; $color-bg: #010B15; $color-fg: $white; @@ -202,4 +203,7 @@ $grid-hover-fg-color: #010B15; $btn-copied-color-fg: #010B15; - +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $black !default; +$quick-search-a-text-color: $black !default; +$quick-search-info-icon: #8A8A8A !default; diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 1552166d2..ef9116958 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -582,6 +582,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Backup of global objects.'), + }, }, { name: 'backup_server', module: this, @@ -591,6 +594,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_global_ctx', module: this, @@ -601,6 +607,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }, { name: 'backup_server_ctx', module: this, @@ -611,6 +620,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_object', module: this, @@ -622,6 +634,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }]; for (var idx = 0; idx < menuUtils.backupSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index a7d056683..914695abf 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -113,6 +113,9 @@ define('pgadmin.datagrid', [ priority: 1, label: gettext('Query Tool'), icon: 'pg-font-icon icon-query-tool', + data:{ + 'data_disabled': gettext('Please select a database from the browser tree to access Query Tool.'), + }, }]; // Create context menu diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index be420826d..c7c7c459c 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -177,6 +177,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.supportedNodes ), + data: { + 'data_disabled': gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'), + }, }]; // Add supported menus into the menus list diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index bef95d114..d225183ce 100644 --- a/web/pgadmin/tools/import_export/static/js/import_export.js +++ b/web/pgadmin/tools/import_export/static/js/import_export.js @@ -412,6 +412,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, ['table'] ), + data: { + 'data_disabled': gettext('Please select any table from the browser tree to Import/Export data.'), + }, }]); }, diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index a8f331094..cf850c76d 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -184,6 +184,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any database from the browser tree to do Maintenance.'), + }, }); } pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index e365b5a76..6c9ff8fda 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -398,6 +398,9 @@ define('tools.restore', [ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any schema/table from the browser tree to Restore data.'), + }, }]; for (var idx = 0; idx < menuUtils.restoreSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/search_objects/static/js/search_objects.js b/web/pgadmin/tools/search_objects/static/js/search_objects.js index fcd52fd7f..ab898bf38 100644 --- a/web/pgadmin/tools/search_objects/static/js/search_objects.js +++ b/web/pgadmin/tools/search_objects/static/js/search_objects.js @@ -36,6 +36,9 @@ define([ enable: this.search_objects_enabled, priority: 1, label: gettext('Search Objects...'), + data: { + 'data_disabled': gettext('Please select a database from the browser tree to search the database objects.'), + }, }, { name: 'search_objects', module: this, diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js new file mode 100644 index 000000000..d7f59563f --- /dev/null +++ b/web/regression/javascript/quick_search/quick_search_spec.js @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { Search } from 'browser/quick_search/trigger_search'; + +let container; + +describe('quick search test cases', function () { + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + ReactDOM.render(<Search />, container); + }); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should have rendered quick-search-container', () => { + expect(container.firstChild.id).toEqual('quick-search-container'); + }); + + it('should have 2 childs in quick-search-container', () => { + expect(container.firstChild.childNodes.length).toEqual(2); + }); + + it('element should be html element', () => { + let inputElement = document.getElementById('live-search-field'); + expect(inputElement instanceof HTMLElement).toBeTruthy(); + }); + +}); diff --git a/web/webpack.config.js b/web/webpack.config.js index 96ef708ab..e6655512a 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -421,6 +421,7 @@ module.exports = [{ use: { loader: 'imports-loader?' + 'pgadmin.dashboard' + + ',pgadmin.browser.quick_search' + ',pgadmin.tools.user_management' + ',pgadmin.browser.object_statistics' + ',pgadmin.browser.dependencies' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 6fc26021e..bddcda9b8 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -181,6 +181,7 @@ var webpackShimConfig = { 'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'), 'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'), 'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'), + 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), [image/png] image001.png (68.4K, 4-image001.png) download | view image [image/png] image002.png (68.4K, 5-image002.png) download | view image [image/png] image003.png (68.4K, 6-image003.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-01-25 10:23 Akshay Joshi <[email protected]> parent: Pramod Ahire <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Akshay Joshi @ 2021-01-25 10:23 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Hi Pramod The patch is not applied, maybe you need to rebase and send the patch again. On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire <[email protected]> wrote: > Hi Team, > > > > Sorry for the inconvenience, kindly please discard previous email. > > > > Please review patch attached to this email. > > Changes as below : > > 1. Added complete quick search menu to help dropdown > 2. Added message to each disabled menu item to describe why it can be > disabled > > 3. Info icon design to menu items results if disabled > 4. Standard/dark/high contrast theme supports > > NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Pramod Ahire <[email protected]> > *Date: *Wednesday, 20 January 2021 at 4:19 PM > *To: *Akshay Joshi <[email protected]>, Dave Page < > [email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi Team, > > > > As per inputs from you all, made changes & attached patch with this email. > > > > Kindly please have a review & do let me know if missed anything. > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Friday, 15 January 2021 at 3:34 PM > *To: *Dave Page <[email protected]> > *Cc: *Pramod Ahire <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > > > > > On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: > > > > > > On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi < > [email protected]> wrote: > > Hi Dave > > > > On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: > > Hi > > > > On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < > [email protected]> wrote: > > Hi Dave, > > > > Thank you for comments ! I had described points below. Please do let me > know if anything missing. > > > > *From: *Dave Page <[email protected]> > *Date: *Thursday, 14 January 2021 at 10:26 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi > > > > On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > > > > Looks very good. I haven't done an extensive code review/test, but two > things spring to mind immediately: > > > > 1) I think the search box should be the top item on the Help menu. I do > not think it should be on the far end of the menu bar, as it looks too much > like it will search for data (think of search on a website). > > > > - As we are showing menu items as well in search results, it can be > redundant for end user & increase duplications of menu items. Please advise > your thoughts on this. > > > > The search box could be on the Help menu, with results shown in a sub-menu > under it. > > > > Another more simple option would be to move the search icon to be directly > after the Help menu (which would hint that it's related to the menus), but > I'm also concerned about the number of items on the top menu - we're > getting to the point that 'common' window sizes would show the hamburger > menu by default. > > > > I personally feel we should not add the search box on the Help menu, > it should be kept separate with a placeholder indicating what is it for. We > can move the search icon after the Help menu. > > > > What is your reasoning? It is, after all, specifically designed to help > people. > > > > OK, I have seen a couple of applications where the search box is > inside the Help menu, agree with your point. > > > > > > > > 2) Do we need another loading icon? Surely there's one in the source tree > already that we can use? > > > > - As we are loading help articles count in background, so I have added > background loading icon to show near to count of results. Our existing icon > is of blue & white circle combination, which will be more useful to show > foreground loading. > > > > Hmm, OK. > > > > > > Please do let me know your valuable inputs on this. > > > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > > > > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image [image/png] image002.png (68.4K, 4-image002.png) download | view image [image/png] image003.png (68.4K, 5-image003.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-02-01 08:02 Pramod Ahire <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 17+ messages in thread From: Pramod Ahire @ 2021-02-01 08:02 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Hi Akshay, Please find the rebased patch as attached with this email. Please do let me know if need anything else. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Monday, 25 January 2021 at 3:53 PM To: Pramod Ahire <[email protected]> Cc: Dave Page <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Pramod The patch is not applied, maybe you need to rebase and send the patch again. On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire <[email protected]> wrote: Hi Team, Sorry for the inconvenience, kindly please discard previous email. Please review patch attached to this email. Changes as below : 1. Added complete quick search menu to help dropdown 2. Added message to each disabled menu item to describe why it can be disabled 3. Info icon design to menu items results if disabled 4. Standard/dark/high contrast theme supports NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Pramod Ahire <[email protected]> Date: Wednesday, 20 January 2021 at 4:19 PM To: Akshay Joshi <[email protected]>, Dave Page <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Team, As per inputs from you all, made changes & attached patch with this email. Kindly please have a review & do let me know if missed anything. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Friday, 15 January 2021 at 3:34 PM To: Dave Page <[email protected]> Cc: Pramod Ahire <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote: Hi Dave On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: Hi On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote: Hi Dave, Thank you for comments ! I had described points below. Please do let me know if anything missing. From: Dave Page <[email protected]> Date: Thursday, 14 January 2021 at 10:26 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). - As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this. The search box could be on the Help menu, with results shown in a sub-menu under it. Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default. I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu. What is your reasoning? It is, after all, specifically designed to help people. OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? - As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading. Hmm, OK. Please do let me know your valuable inputs on this. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 Attachments: [application/octet-stream] quick-search-v4.patch (58.3K, 3-quick-search-v4.patch) download | inline diff: diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index 7f909c41f..235db0271 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -443,6 +443,19 @@ def register_browser_preferences(self): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', 'open_quick_search', + gettext('Quick Search'), 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 70, 'char': 'f'} + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=fields + ) + self.dynamic_tab_title = self.preference.register( 'tab_settings', 'dynamic_tabs', gettext("Dynamic tab size"), 'boolean', False, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js index a62ad0b80..e47b54cb1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js @@ -48,19 +48,19 @@ define('pgadmin.node.package', [ name: 'create_package_on_coll', node: 'coll-package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), - icon: 'wcTabIcon icon-package', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js index 0523709a0..316f3d70f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js @@ -47,19 +47,19 @@ define('pgadmin.node.synonym', [ name: 'create_synonym_on_coll', node: 'coll-synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), - icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js index 57d01b5a0..6fa2613d0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -54,25 +54,25 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_on_coll', node: 'coll-compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onTable', node: 'table', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onPartition', node: 'partition', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'enable_compound_trigger', node: 'compound_trigger', module: this, @@ -88,7 +88,7 @@ define('pgadmin.node.compound_trigger', [ name: 'create_compound_trigger_onView', node: 'view', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), - icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index f322d0888..7e07f443e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -76,12 +76,16 @@ define('pgadmin.node.database', [ name: 'connect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'connect_database', category: 'connect', priority: 4, label: gettext('Connect Database...'), - icon: 'fa fa-link', enable : 'is_not_connected', + icon: 'fa fa-link', enable : 'is_not_connected', data: { + 'data_disabled': gettext('Selected database is already connected.'), + }, },{ name: 'disconnect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'disconnect_database', category: 'drop', priority: 5, label: gettext('Disconnect Database...'), - icon: 'fa fa-unlink', enable : 'is_connected', + icon: 'fa fa-unlink', enable : 'is_connected',data: { + 'data_disabled': gettext('Selected database is already disconnected.'), + }, },{ name: 'generate_erd', node: 'database', module: this, applies: ['object', 'context'], callback: 'generate_erd', diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js index 50156ca73..053072e5c 100644 --- a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js +++ b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js @@ -48,7 +48,7 @@ define('pgadmin.node.resource_group', [ name: 'create_resourcegroup_on_server', node: 'server', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, /* Function is used to check the server type and version. * Resource Group only supported in PPAS 9.4 and above. */ @@ -62,12 +62,12 @@ define('pgadmin.node.resource_group', [ name: 'create_resource_group_on_coll', node: 'coll-resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, },{ name: 'create_resource_group', node: 'resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), - icon: 'wcTabIcon icon-resource_group', data: {action: 'create'}, + icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')}, }, ]); }, diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 13c00c4dc..72da6ca2d 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -100,12 +100,16 @@ define('pgadmin.node.server', [ name: 'reload_configuration', node: 'server', module: this, applies: ['tools', 'context'], callback: 'reload_configuration', category: 'reload', priority: 6, label: gettext('Reload Configuration'), - icon: 'fa fa-redo-alt', enable : 'enable_reload_config', + icon: 'fa fa-redo-alt', enable : 'enable_reload_config',data: { + 'data_disabled': gettext('Please select a server from the browser tree to reload the configuration files.'), + }, },{ name: 'restore_point', node: 'server', module: this, applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'), - icon: 'fa fa-anchor', enable : 'is_applicable', + icon: 'fa fa-anchor', enable : 'is_applicable',data: { + 'data_disabled': gettext('Please select any server from the browser tree to Add Named Restore Point.'), + }, },{ name: 'change_password', node: 'server', module: this, applies: ['object'], callback: 'change_password', @@ -115,12 +119,16 @@ define('pgadmin.node.server', [ name: 'wal_replay_pause', node: 'server', module: this, applies: ['tools', 'context'], callback: 'pause_wal_replay', category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'), - icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled', + icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'), + }, },{ name: 'wal_replay_resume', node: 'server', module: this, applies: ['tools', 'context'], callback: 'resume_wal_replay', category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'), - icon: 'fa fa-play-circle', enable : 'wal_resume_enabled', + icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',data: { + 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'), + }, },{ name: 'clear_saved_password', node: 'server', module: this, applies: ['object', 'context'], callback: 'clear_saved_password', @@ -145,6 +153,9 @@ define('pgadmin.node.server', [ } return false; }, + data: { + 'data_disabled': gettext('SSH Tunnel password is not saved for selected server.'), + }, }]); _.bindAll(this, 'connection_lost'); diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 475412bf7..6ae092bbb 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -875,6 +875,10 @@ define('pgadmin.browser', [ var $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> SUGGESTED SITES</div>'); + } if (pgAdmin.Browser.MenuCreator( obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories @@ -902,6 +906,7 @@ define('pgadmin.browser', [ } else if(type == 'dialog_help') { window.open(url, 'pgadmin_help'); } + $('#live-search-field').focus(); }, _findTreeChildNode: function(_i, _d, _o) { var loaded = _o.t.wasLoad(_i), @@ -2224,6 +2229,8 @@ define('pgadmin.browser', [ if (pgBrowser.utils.useSpaces == 'True') { pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab'; } - + setTimeout(function(){ + $('#mnu_about').closest('li').before('<li class="dropdown-divider"></li>'); + }, 100); return pgAdmin.Browser; }); diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 3381162d0..a69c7009f 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value), 'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value), 'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), + 'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), }; this.shortcutMethods = { @@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple 'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row + 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; this.bindShortcuts(); } @@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, { return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut); }, + bindOpenQuickSearch: function() { + $('#search_icon').trigger('click'); + }, }); module.exports = pgAdmin.Browser.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index 53d40dcbe..b2f12ff47 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -67,12 +67,17 @@ define([ }, this.menu_items); this.$el = create_submenu.$el; } else { + var data_disabled = null; + if(this.data != undefined && this.data.data_disabled != undefined){ + data_disabled = this.data.data_disabled; + } var url = $('<a></a>', { 'id': this.name, 'href': this.url, 'target': this.target, 'data-toggle': 'pg-menu', 'role': 'menuitem', + 'data-disabled': data_disabled, }).data('pgMenu', { module: this.module || pgAdmin.Browser, cb: this.callback, diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 12226fe2f..21ff1bf87 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -150,6 +150,7 @@ define('pgadmin.browser.node', [ label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'), data: { 'url': 'drop', + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-trash-alt', enable: _.isFunction(self.canDrop) ? @@ -232,6 +233,7 @@ define('pgadmin.browser.node', [ category: gettext('Scripts'), data: { 'script': stype, + 'data_disabled': gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-pencil-alt', enable: self.check_user_permission, diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js new file mode 100644 index 000000000..5f0120407 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search.js @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, 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); + +// Entry point - Quick search functionality +/*if (document.getElementById('quick-search-component')) { + ReactDOM.render( + <Search onResult={onResultFetch} />, + document.getElementById('quick-search-component') + ); +}*/ diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js new file mode 100644 index 000000000..bf9f8c059 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +// Allow us to render IFrame using React +// Here we will add the event listener on Iframe load event +export class Iframe extends Component { + static get propTypes() { + return { + id: PropTypes.string.isRequired, + srcURL: PropTypes.string.isRequired, + onLoad: PropTypes.func.isRequired, + }; + } + + render () { + const iframeStyle = { + border: '0', + display: 'block', + position:'absolute', + opacity:'0', + }; + const {id, srcURL, onLoad} = this.props; + + return ( + <iframe + id={id} + src={srcURL} + onLoad={onLoad} + width={'20'} + height={'20'} + style={iframeStyle} + /> + ); + } +} diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js new file mode 100644 index 000000000..f3d08aecc --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js @@ -0,0 +1,91 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import gettext from 'sources/gettext'; + + + +// 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; +}; + +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); + } else { + parseLI(_menu, path); + } + } + }); + }; + + // 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); + + mainMenus.forEach((menu) => { + iterItem(menu, getMenuName(menu)); + }); +} diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js new file mode 100644 index 000000000..9808ad007 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/online_help.js @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React from 'react'; +import ReactDOM from 'react-dom'; +import {Iframe} from './iframe_component'; +import url_for from 'sources/url_for'; + +const extractSearchResult = (list) => { + let result = {}; + for (let idx = 0; idx < list.length; idx++) { + let link = list[idx].getElementsByTagName('A'); + // we are not going to display more than first 10 result as per design + if (link.length == 0) { + break; + } + let topicName = link[0].text; + let topicLink = url_for('help.static', { + 'filename': link[0].getAttribute('href'), + }); + result[topicName] = topicLink; + } + return result; +}; + +export function onlineHelpSearch(param, props) { + param = param.split(' ').join('+'); + const setState = props.setState; + const helpURL = url_for('help.static', { + 'filename': 'search.html', + }); + const srcURL = `${helpURL}?q=${param}`; + let isIFrameLoaded = false; + if(document.getElementById('hidden-quick-search-iframe')){ + document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true); + } + + // Below function will be called when the page will be loaded in Iframe + const _iframeLoaded = () => { + if (isIFrameLoaded) { + return false; + } + isIFrameLoaded = true; + let iframe = document.getElementById('hidden-quick-search-iframe'); + let content = (iframe.contentWindow || iframe.contentDocument); + 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); + 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: {}, + })); + ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container')); + isIFrameLoaded = false; + window.clearInterval(pooling); + } + }, 500); + }; + + // Render IFrame + ReactDOM.render( + <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>, + document.getElementById('quick-search-iframe-container'), + ); +} diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js new file mode 100644 index 000000000..7c94dbd93 --- /dev/null +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, {useRef,useState, useEffect} from 'react'; +import {useDelayDebounce} from 'sources/custom_hooks'; +import {onlineHelpSearch} from './online_help'; +import {menuSearch} from './menuitems_help'; +import $ from 'jquery'; +import gettext from 'sources/gettext'; + +export function Search() { + const wrapperRef = useRef(null); + const [searchTerm, setSearchTerm] = useState(''); + const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); + let helpLinkTitles = []; + let helpLinks = []; + const [isMenuLoading, setIsMenuLoading] = useState(false); + const [isHelpLoading, setIsHelpLoading] = useState(false); + const [menuSearchResult, setMenuSearchResult] = useState({ + fetched: false, + data: [], + }); + const [helpSearchResult, setHelpSearchResult] = useState({ + fetched: false, + clearedPooling: true, + url: '', + data: [], + }); + + const [showResults, setShowResults] = useState(false); + + const resetSearchState = () => { + setMenuSearchResult(state => ({ + ...state, + fetched: false, + data: [], + })); + setHelpSearchResult(state => ({ + ...state, + fetched: false, + clearedPooling: true, + url: '', + data: {}, + })); + }; + + // Below will be called when any changes has been made to state + useEffect(() => { + helpLinkTitles = Object.keys(helpSearchResult.data); + for(let i = 0; i<helpLinkTitles.length;i++){ + helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>); + } + + if(menuSearchResult.fetched == true){ + setIsMenuLoading(false); + } + + if(helpSearchResult.fetched == true){ + setIsHelpLoading(false); + } + }, [menuSearchResult, helpSearchResult]); + + const initSearch = (param) => { + setIsMenuLoading(true); + setIsHelpLoading(true); + + onlineHelpSearch(param, { + state: helpSearchResult, + setState: setHelpSearchResult, + }); + menuSearch(param, { + state: menuSearchResult, + setState: setMenuSearchResult, + }); + }; + + + // Debounse logic to avoid multiple re-render with each keypress + useDelayDebounce(initSearch, searchTerm, 1000); + + const toggleDropdownMenu = () => { + let pooling = window.pooling; + if(pooling){ + window.clearInterval(pooling); + } + document.getElementsByClassName('live-search-field')[0].value = ''; + setTimeout(function(){ + document.getElementById('live-search-field').focus(); + },100); + resetSearchState(); + setShowResults(!showResults); + setIsMenuLoading(false); + setIsHelpLoading(false); + setIsShowMinLengthMsg(false); + }; + + const refactorMenuItems = (items) => { + if(items.length > 0){ + let menuItemsHtmlElement = []; + for(let i=0; i < items.length; i++){ + Object.keys(items[i]).map( (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') == true ? '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') == true && 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>); + } + }); + } + $('[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; + if(pooling){ + window.clearInterval(pooling); + } + resetSearchState(); + setSearchTerm(''); + if(value.length >= 3){ + setSearchTerm(value); + setIsMenuLoading(true); + setIsHelpLoading(true); + setIsShowMinLengthMsg(false); + }else{ + setIsMenuLoading(false); + setIsHelpLoading(false); + } + + if(value.length < 3 && value.length > 0){ + setIsShowMinLengthMsg(true); + } + + if(value.length == 0){ + setIsShowMinLengthMsg(false); + } + }; + + const useOutsideAlerter = (ref) => { + useEffect(() => { + /** + * Alert if clicked on outside of element + */ + function handleClickOutside(event) { + if (ref.current && !ref.current.contains(event.target)) { + let input_element = document.getElementById('live-search-field'); + let input_value = input_element.value; + if(input_value && input_value.length > 0){ + toggleDropdownMenu(); + } + return; + } + } + // Bind the event listener + document.addEventListener('mousedown', handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref]); + }; + + useOutsideAlerter(wrapperRef); + + return ( + <div id='quick-search-container' onClick={setSearchTerm}></div>, + <ul id='quick-search-container' ref={wrapperRef} className='test' 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' + aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } /> + </li> + <div style={{marginBottom:0}}> + <div> + + { isShowMinLengthMsg + ? (<div className='pad-12 no-results'> + <span className='fa fa-info-circle'></span> + Please enter minimum 3 characters to search + </div>) + :''} + <div > + + { (menuSearchResult.fetched == true && isMenuLoading == false ) ? + <div> + <div className='menu-groups'> + <span className='fa fa-window-maximize'></span> {gettext('MENU ITEMS')} ({menuSearchResult.data.length}) + </div> + + + {refactorMenuItems(menuSearchResult.data)} + </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')} + + {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + + { (helpSearchResult.fetched == true && isHelpLoading == false) ? + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> {gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ? + <span>(10 of {Object.keys(helpSearchResult.data).length} ) + </span>: + '(' + Object.keys(helpSearchResult.data).length + ')'} + { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''} + { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} <span className='fas fa-external-link-alt' ></span></a> : ''} + </div> + + {Object.keys(helpSearchResult.data).map( (value, index) => { + if(index <= 9) { return <li key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; } + })} + + {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''} + </div> : ( (isHelpLoading && isMenuLoading == false) ? ( + <div> + <div className='help-groups'> + <span className='fa fa-question-circle'></span> + HELP ARTICLES + {Object.keys(helpSearchResult.data).length > 10 + ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')' + : '(' + Object.keys(helpSearchResult.data).length + ')' + } + { Object.keys(helpSearchResult.data).length > 10 + ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'> + Show all <span className='fas fa-external-link-alt' ></span></a> : '' + } + </div> + <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> + </div>) : '')} + </div> + </div> + </div> + </ul> + </li> + <div id='quick-search-iframe-container' /> + </ul> + ); + +} diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss new file mode 100644 index 000000000..ea90a62d2 --- /dev/null +++ b/web/pgadmin/browser/static/scss/_quick_search.scss @@ -0,0 +1,151 @@ +#myInput { + box-sizing: border-box; + background-position: 14px 12px; + background-repeat: no-repeat; + font-size: 16px; + padding: 14px 20px 12px 45px; + border: none; + border-bottom: 1px solid #ddd; +} + +#myInput:focus {outline: 3px solid #ddd;} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + background-color: $color-bg; + min-width: 376px; + overflow: auto; + z-index: 1; +} + +.custom-dropdown-content a { + color: $dropdown-link-color; + padding: 6px 12px 6px 16px; + text-decoration: none; + display: block; + cursor:pointer; +} + +.custom-dropdown-content a:hover { + color: $black; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;} + +.search_icon{ + color: $white; + cursor: pointer; + padding-right: 8px; +} +.hidden { display:none; } + +.visible { display:block; } + +.menu-groups, .help-groups{ + background-color: $color-gray-light; + padding: 6px; + font-size: 12px; + font-weight: 600; +} + +.menu-groups .fa, .fas{ + font-weight:normal !important; +} + +.help-groups .fa, .fas{ + font-weight:600 !important; +} + +.pad-12{ + padding:12px; +} + +.no-results{ + font-size: 14px; + color: #697986; + text-align: center; +} + +.no-padding{ + padding:0 !important; +} + +.menu-groups-a{ + display:flex !important; + flex-direction:column; + padding: 6px; + color: $dropdown-link-color; + padding: 6px 16px; +} + +.menu-groups-a span{ + font-size: 0.9em; + font-weight: 100; + color: $quick-search-span-text; +} + +#myDropdown a:hover span{ + color: $color-danger-fg !important; +} + +.search-icon{ + background: $loader-icon-small center center no-repeat; + margin: auto !important; + height: 22px !important; + width: 130px !important; + background-position: left !important; + font-size: 14px; + color: $dropdown-link-color; + padding-left: 30px; +} + +.help_loading_icon{ + height: 16px; +} +#myDropdown ul { + list-style: none; +} + +.border-right-search-icon{ + border-right: 2px solid #fff; +} + +.help_submenu{ + left: 100%; + width: 20rem; + top:-0.4rem; +} + +.help_menu{ + min-width:300px; +} + +.dropdown-item-input{ + padding:4px; +} + +.menu-groups-a span:focus{ + color:$color-primary-fg; +} + +.dropdown-item:hover, .dropdown-item:focus span{ + color: $color-danger-fg !important; +} + +.quick-search-tooltip{ + position: absolute; + right: 10px; + margin-top: -2.1em; + font-size: 16px; + cursor:pointer; + color: $quick-search-info-icon; +} + +#myDropdown .dropdown-divider{ + height: auto; + border-top: 0; +} diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index c571df246..c1387dad5 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -133,7 +133,7 @@ window.onload = function(e){ <li id="mnu_help" class="nav-item active dropdown d-none"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Help') }} <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"></ul> + <ul class="dropdown-menu help_menu" role="menu"></ul> </li> </ul> {% if config.SERVER_MODE %} diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 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 Bt<?Yk literal 0 HcmV?d00001 diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 9163f0d6c..b99dcbab4 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -26,4 +26,16 @@ export function usePrevious(value) { ref.current = value; }); return ref.current; -} \ No newline at end of file +} + +export function useDelayDebounce(callback, args, delay) { + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + if (args) { + callback(args); + } + }, delay); + return () => clearTimeout(delayDebounceFn); + }, [args]); +} + diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss index f8f2bcf99..2e8f46ad8 100644 --- a/web/pgadmin/static/scss/_pgadmin.style.scss +++ b/web/pgadmin/static/scss/_pgadmin.style.scss @@ -1129,3 +1129,10 @@ select:-webkit-autofill:focus { .pull-left{ float:left } + +.menu-groups-a:hover span{ + color: $white !important; +} + +#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;} + diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss index 46dcb1d6f..1e357b312 100644 --- a/web/pgadmin/static/scss/resources/_default.variables.scss +++ b/web/pgadmin/static/scss/resources/_default.variables.scss @@ -4,6 +4,9 @@ $enable-flex: true; $white: #fff; $black: #000; +$span-text-color: #6B6B6B !default; +$span-text-color-hover: #6B6B6B !default; + $color-bg: $white !default; $color-fg: #222222 !default; @@ -349,6 +352,10 @@ $grid-hover-fg-color: $color-fg !default; $btn-copied-color-fg: $active-color !default; +$quick-search-a-text-color: $black !default; +$quick-search-span-text: $span-text-color !default; +$quick-search-span-text-hover: $span-text-color-hover !default; +$quick-search-info-icon: #646C82 !default; /** ERD **/ $erd-row-padding: 0.25rem; $erd-node-border-color: $border-color !default; diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss index d8ca65b50..6912060bb 100644 --- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss @@ -122,6 +122,11 @@ $datagrid-selected-color: $color-primary-fg; $select2-placeholder: #999; +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $white !default; +$quick-search-a-text-color: $white !default; +$quick-search-info-icon: #8A8A8A !default; + /* ERD */ $erd-row-padding: 0.25rem; $erd-node-border-color: $border-color; diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss index 75daf4a35..2d26af1fe 100644 --- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss +++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss @@ -1,4 +1,5 @@ $white: #FFFFFF; +$black: #000; $color-bg: #010B15; $color-fg: $white; @@ -200,4 +201,7 @@ $grid-hover-fg-color: #010B15; $btn-copied-color-fg: #010B15; - +$span-text-color: #9D9FA1 !default; +$span-text-color-hover: $black !default; +$quick-search-a-text-color: $black !default; +$quick-search-info-icon: #8A8A8A !default; diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 1552166d2..ef9116958 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -582,6 +582,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Backup of global objects.'), + }, }, { name: 'backup_server', module: this, @@ -591,6 +594,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_global_ctx', module: this, @@ -601,6 +607,9 @@ define([ label: gettext('Backup Globals...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }, { name: 'backup_server_ctx', module: this, @@ -611,6 +620,9 @@ define([ label: gettext('Backup Server...'), icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, + data: { + 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + }, }, { name: 'backup_object', module: this, @@ -622,6 +634,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'), + }, }]; for (var idx = 0; idx < menuUtils.backupSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index c9b8b5b7e..6db4c9922 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -113,6 +113,9 @@ define('pgadmin.datagrid', [ priority: 1, label: gettext('Query Tool'), icon: 'pg-font-icon icon-query-tool', + data:{ + 'data_disabled': gettext('Please select a database from the browser tree to access Query Tool.'), + }, }]; // Create context menu diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index be420826d..c7c7c459c 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -177,6 +177,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.supportedNodes ), + data: { + 'data_disabled': gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'), + }, }]; // Add supported menus into the menus list diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index bef95d114..d225183ce 100644 --- a/web/pgadmin/tools/import_export/static/js/import_export.js +++ b/web/pgadmin/tools/import_export/static/js/import_export.js @@ -412,6 +412,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, ['table'] ), + data: { + 'data_disabled': gettext('Please select any table from the browser tree to Import/Export data.'), + }, }]); }, diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index a8f331094..cf850c76d 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -184,6 +184,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any database from the browser tree to do Maintenance.'), + }, }); } pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index e365b5a76..6c9ff8fda 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -398,6 +398,9 @@ define('tools.restore', [ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes ), + data: { + 'data_disabled': gettext('Please select any schema/table from the browser tree to Restore data.'), + }, }]; for (var idx = 0; idx < menuUtils.restoreSupportedNodes.length; idx++) { diff --git a/web/pgadmin/tools/search_objects/static/js/search_objects.js b/web/pgadmin/tools/search_objects/static/js/search_objects.js index fcd52fd7f..ab898bf38 100644 --- a/web/pgadmin/tools/search_objects/static/js/search_objects.js +++ b/web/pgadmin/tools/search_objects/static/js/search_objects.js @@ -36,6 +36,9 @@ define([ enable: this.search_objects_enabled, priority: 1, label: gettext('Search Objects...'), + data: { + 'data_disabled': gettext('Please select a database from the browser tree to search the database objects.'), + }, }, { name: 'search_objects', module: this, diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js new file mode 100644 index 000000000..d7f59563f --- /dev/null +++ b/web/regression/javascript/quick_search/quick_search_spec.js @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { Search } from 'browser/quick_search/trigger_search'; + +let container; + +describe('quick search test cases', function () { + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + ReactDOM.render(<Search />, container); + }); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + it('should have rendered quick-search-container', () => { + expect(container.firstChild.id).toEqual('quick-search-container'); + }); + + it('should have 2 childs in quick-search-container', () => { + expect(container.firstChild.childNodes.length).toEqual(2); + }); + + it('element should be html element', () => { + let inputElement = document.getElementById('live-search-field'); + expect(inputElement instanceof HTMLElement).toBeTruthy(); + }); + +}); diff --git a/web/webpack.config.js b/web/webpack.config.js index c225b31b8..883b59006 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -422,6 +422,7 @@ module.exports = [{ use: { loader: 'imports-loader?' + 'pgadmin.dashboard' + + ',pgadmin.browser.quick_search' + ',pgadmin.tools.user_management' + ',pgadmin.browser.object_statistics' + ',pgadmin.browser.dependencies' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index cc3b76a92..a2a5a92db 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -186,6 +186,7 @@ var webpackShimConfig = { 'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'), 'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'), 'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'), + 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), [image/png] image001.png (68.4K, 4-image001.png) download | view image [image/png] image002.png (68.4K, 5-image002.png) download | view image [image/png] image003.png (68.4K, 6-image003.png) download | view image [image/png] image004.png (68.4K, 7-image004.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-02-02 09:18 Akshay Joshi <[email protected]> parent: Pramod Ahire <[email protected]> 0 siblings, 2 replies; 17+ messages in thread From: Akshay Joshi @ 2021-02-02 09:18 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Thanks, patch applied with some string modifications. On Mon, Feb 1, 2021 at 1:32 PM Pramod Ahire <[email protected]> wrote: > Hi Akshay, > > > > Please find the rebased patch as attached with this email. > > > > Please do let me know if need anything else. > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Monday, 25 January 2021 at 3:53 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *Dave Page <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi Pramod > > > > The patch is not applied, maybe you need to rebase and send the patch > again. > > > > On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Sorry for the inconvenience, kindly please discard previous email. > > > > Please review patch attached to this email. > > Changes as below : > > 1. Added complete quick search menu to help dropdown > 2. Added message to each disabled menu item to describe why it can be > disabled > > 3. Info icon design to menu items results if disabled > 4. Standard/dark/high contrast theme supports > > NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Pramod Ahire <[email protected]> > *Date: *Wednesday, 20 January 2021 at 4:19 PM > *To: *Akshay Joshi <[email protected]>, Dave Page < > [email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi Team, > > > > As per inputs from you all, made changes & attached patch with this email. > > > > Kindly please have a review & do let me know if missed anything. > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Friday, 15 January 2021 at 3:34 PM > *To: *Dave Page <[email protected]> > *Cc: *Pramod Ahire <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > > > > > On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: > > > > > > On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi < > [email protected]> wrote: > > Hi Dave > > > > On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: > > Hi > > > > On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < > [email protected]> wrote: > > Hi Dave, > > > > Thank you for comments ! I had described points below. Please do let me > know if anything missing. > > > > *From: *Dave Page <[email protected]> > *Date: *Thursday, 14 January 2021 at 10:26 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi > > > > On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > > > > Looks very good. I haven't done an extensive code review/test, but two > things spring to mind immediately: > > > > 1) I think the search box should be the top item on the Help menu. I do > not think it should be on the far end of the menu bar, as it looks too much > like it will search for data (think of search on a website). > > > > - As we are showing menu items as well in search results, it can be > redundant for end user & increase duplications of menu items. Please advise > your thoughts on this. > > > > The search box could be on the Help menu, with results shown in a sub-menu > under it. > > > > Another more simple option would be to move the search icon to be directly > after the Help menu (which would hint that it's related to the menus), but > I'm also concerned about the number of items on the top menu - we're > getting to the point that 'common' window sizes would show the hamburger > menu by default. > > > > I personally feel we should not add the search box on the Help menu, > it should be kept separate with a placeholder indicating what is it for. We > can move the search icon after the Help menu. > > > > What is your reasoning? It is, after all, specifically designed to help > people. > > > > OK, I have seen a couple of applications where the search box is > inside the Help menu, agree with your point. > > > > > > > > 2) Do we need another loading icon? Surely there's one in the source tree > already that we can use? > > > > - As we are loading help articles count in background, so I have added > background loading icon to show near to count of results. Our existing icon > is of blue & white circle combination, which will be more useful to show > foreground loading. > > > > Hmm, OK. > > > > > > Please do let me know your valuable inputs on this. > > > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > > > > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image [image/png] image002.png (68.4K, 4-image002.png) download | view image [image/png] image003.png (68.4K, 5-image003.png) download | view image [image/png] image004.png (68.4K, 6-image004.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-02-03 05:56 Pramod Ahire <[email protected]> parent: Akshay Joshi <[email protected]> 1 sibling, 1 reply; 17+ messages in thread From: Pramod Ahire @ 2021-02-03 05:56 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Hi Akshay, As reported jasmine test case failure for quick search, had fixed issue & attached here in email. Kindly also find below updates in the patch: 1. Jasmine test cases fixed 2. Added missing messages for disabled menu items 3. Removed single quotes from ‘data_disabled’ -> data_disabled Please review & do let me know if missed anything. Thanks! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Tuesday, 2 February 2021 at 2:48 PM To: Pramod Ahire <[email protected]> Cc: Dave Page <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Thanks, patch applied with some string modifications. On Mon, Feb 1, 2021 at 1:32 PM Pramod Ahire <[email protected]> wrote: Hi Akshay, Please find the rebased patch as attached with this email. Please do let me know if need anything else. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Monday, 25 January 2021 at 3:53 PM To: Pramod Ahire <[email protected]> Cc: Dave Page <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Pramod The patch is not applied, maybe you need to rebase and send the patch again. On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire <[email protected]> wrote: Hi Team, Sorry for the inconvenience, kindly please discard previous email. Please review patch attached to this email. Changes as below : 1. Added complete quick search menu to help dropdown 2. Added message to each disabled menu item to describe why it can be disabled 3. Info icon design to menu items results if disabled 4. Standard/dark/high contrast theme supports NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Pramod Ahire <[email protected]> Date: Wednesday, 20 January 2021 at 4:19 PM To: Akshay Joshi <[email protected]>, Dave Page <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi Team, As per inputs from you all, made changes & attached patch with this email. Kindly please have a review & do let me know if missed anything. Thanks ! Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com From: Akshay Joshi <[email protected]> Date: Friday, 15 January 2021 at 3:34 PM To: Dave Page <[email protected]> Cc: Pramod Ahire <[email protected]>, pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote: Hi Dave On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: Hi On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote: Hi Dave, Thank you for comments ! I had described points below. Please do let me know if anything missing. From: Dave Page <[email protected]> Date: Thursday, 14 January 2021 at 10:26 PM To: Pramod Ahire <[email protected]> Cc: pgadmin-hackers <[email protected]> Subject: Re: Quick search for menu items & help articles Hi On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote: Hi Team, Please find the attached designs & patch that contains complete functionality except below to do for quick search. To Do: Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. Please do let me know if I missed anything or suggestion of yours. Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately: 1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website). - As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this. The search box could be on the Help menu, with results shown in a sub-menu under it. Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default. I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu. What is your reasoning? It is, after all, specifically designed to help people. OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. 2) Do we need another loading icon? Surely there's one in the source tree already that we can use? - As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading. Hmm, OK. Please do let me know your valuable inputs on this. Pramod Ahire Software Engineer C: +91-020-66449600/601 D: +91-9028697679 edbpostgres.com -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Dave Page Blog: http://pgsnake.blogspot.com Twitter: @pgsnake EDB: http://www.enterprisedb.com -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 -- Thanks & Regards Akshay Joshi pgAdmin Hacker | Principal Software Architect EDB Postgres Mobile: +91 976-788-8246 Attachments: [application/octet-stream] quick_search_jasmine_fixes_v1.patch (26.6K, 3-quick_search_jasmine_fixes_v1.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js index ccdf46d90..52bcd8386 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js @@ -49,21 +49,21 @@ define('pgadmin.node.package', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'package', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_package', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Package...'), icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js index 4addef5e6..3ce6129b1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js @@ -48,21 +48,21 @@ define('pgadmin.node.synonym', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'synonym', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_synonym', node: 'schema', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Synonym...'), icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js index 55d3119f6..e46f53a25 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -55,28 +55,28 @@ define('pgadmin.node.compound_trigger', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger', node: 'compound_trigger', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onTable', node: 'table', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'create_compound_trigger_onPartition', node: 'partition', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', },{ name: 'enable_compound_trigger', node: 'compound_trigger', module: this, @@ -93,7 +93,7 @@ define('pgadmin.node.compound_trigger', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Compound Trigger...'), icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, enable: 'canCreate', }, ]); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js index 02ea36571..e236230a3 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js @@ -73,11 +73,17 @@ function( applies: ['object', 'context'], callback: 'enable_triggers_on_table', category: gettext('Trigger(s)'), priority: 4, label: gettext('Enable All'), icon: 'fa fa-check', enable : 'canCreate_with_trigger_enable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + }, },{ name: 'disable_all_triggers', node: 'partition', module: this, applies: ['object', 'context'], callback: 'disable_triggers_on_table', category: gettext('Trigger(s)'), priority: 4, label: gettext('Disable All'), icon: 'fa fa-times', enable : 'canCreate_with_trigger_disable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + }, },{ name: 'reset_table_stats', node: 'partition', module: this, applies: ['object', 'context'], callback: 'reset_table_stats', diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js index b4a23c4ee..9cf9cf451 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js @@ -95,11 +95,17 @@ define('pgadmin.node.table', [ applies: ['object', 'context'], callback: 'enable_triggers_on_table', category: gettext('Trigger(s)'), priority: 4, label: gettext('Enable All'), icon: 'fa fa-check', enable : 'canCreate_with_trigger_enable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + }, },{ name: 'disable_all_triggers', node: 'table', module: this, applies: ['object', 'context'], callback: 'disable_triggers_on_table', category: gettext('Trigger(s)'), priority: 4, label: gettext('Disable All'), icon: 'fa fa-times', enable : 'canCreate_with_trigger_disable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + }, },{ name: 'reset_table_stats', node: 'table', module: this, applies: ['object', 'context'], callback: 'reset_table_stats', diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js index 9334edcd7..c53f04429 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js +++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js @@ -77,14 +77,14 @@ define('pgadmin.node.database', [ applies: ['object', 'context'], callback: 'connect_database', category: 'connect', priority: 4, label: gettext('Connect Database...'), icon: 'fa fa-link', enable : 'is_not_connected', data: { - 'data_disabled': gettext('Selected database is already connected.'), + data_disabled: gettext('Selected database is already connected.'), }, },{ name: 'disconnect_database', node: 'database', module: this, applies: ['object', 'context'], callback: 'disconnect_database', category: 'drop', priority: 5, label: gettext('Disconnect Database...'), icon: 'fa fa-unlink', enable : 'is_connected',data: { - 'data_disabled': gettext('Selected database is already disconnected.'), + data_disabled: gettext('Selected database is already disconnected.'), }, },{ name: 'generate_erd', node: 'database', module: this, diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js index 2752761d2..2087c8ffc 100644 --- a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js +++ b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js @@ -49,7 +49,7 @@ define('pgadmin.node.resource_group', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), icon: 'wcTabIcon icon-resource_group', data: {action: 'create', - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, /* Function is used to check the server type and version. * Resource Group only supported in PPAS 9.4 and above. */ @@ -64,13 +64,13 @@ define('pgadmin.node.resource_group', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), icon: 'wcTabIcon icon-resource_group', data: {action: 'create', - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, },{ name: 'create_resource_group', node: 'resource_group', module: this, applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Resource Group...'), icon: 'wcTabIcon icon-resource_group', data: {action: 'create', - 'data_disabled': gettext('This option is only available on EPAS servers.')}, + data_disabled: gettext('This option is only available on EPAS servers.')}, }, ]); }, diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 72da6ca2d..074077069 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -90,44 +90,50 @@ define('pgadmin.node.server', [ name: 'connect_server', node: 'server', module: this, applies: ['object', 'context'], callback: 'connect_server', category: 'connect', priority: 4, label: gettext('Connect Server'), - icon: 'fa fa-link', enable : 'is_not_connected', + icon: 'fa fa-link', enable : 'is_not_connected',data: { + data_disabled: gettext('Database is already connected.'), + }, },{ name: 'disconnect_server', node: 'server', module: this, applies: ['object', 'context'], callback: 'disconnect_server', category: 'drop', priority: 5, label: gettext('Disconnect Server'), - icon: 'fa fa-unlink', enable : 'is_connected', + icon: 'fa fa-unlink', enable : 'is_connected',data: { + data_disabled: gettext('Database is already disconnected.'), + }, },{ name: 'reload_configuration', node: 'server', module: this, applies: ['tools', 'context'], callback: 'reload_configuration', category: 'reload', priority: 6, label: gettext('Reload Configuration'), icon: 'fa fa-redo-alt', enable : 'enable_reload_config',data: { - 'data_disabled': gettext('Please select a server from the browser tree to reload the configuration files.'), + data_disabled: gettext('Please select a server from the browser tree to reload the configuration files.'), }, },{ name: 'restore_point', node: 'server', module: this, applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'), icon: 'fa fa-anchor', enable : 'is_applicable',data: { - 'data_disabled': gettext('Please select any server from the browser tree to Add Named Restore Point.'), + data_disabled: gettext('Please select any server from the browser tree to Add Named Restore Point.'), }, },{ name: 'change_password', node: 'server', module: this, applies: ['object'], callback: 'change_password', label: gettext('Change Password...'), priority: 10, - icon: 'fa fa-lock', enable : 'is_connected', + icon: 'fa fa-lock', enable : 'is_connected',data: { + data_disabled: gettext('Please connect server to enable change password. '), + }, },{ name: 'wal_replay_pause', node: 'server', module: this, applies: ['tools', 'context'], callback: 'pause_wal_replay', category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'), icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',data: { - 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'), + data_disabled: gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'), }, },{ name: 'wal_replay_resume', node: 'server', module: this, applies: ['tools', 'context'], callback: 'resume_wal_replay', category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'), icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',data: { - 'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'), + data_disabled: gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'), }, },{ name: 'clear_saved_password', node: 'server', module: this, @@ -154,7 +160,7 @@ define('pgadmin.node.server', [ return false; }, data: { - 'data_disabled': gettext('SSH Tunnel password is not saved for selected server.'), + data_disabled: gettext('SSH Tunnel password is not saved for selected server.'), }, }]); diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 21ff1bf87..201dfbfcf 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -150,7 +150,7 @@ define('pgadmin.browser.node', [ label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'), data: { 'url': 'drop', - 'data_disabled': gettext('The selected tree node does not support this option.'), + data_disabled: gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-trash-alt', enable: _.isFunction(self.canDrop) ? @@ -233,7 +233,7 @@ define('pgadmin.browser.node', [ category: gettext('Scripts'), data: { 'script': stype, - 'data_disabled': gettext('The selected tree node does not support this option.'), + data_disabled: gettext('The selected tree node does not support this option.'), }, icon: 'fa fa-pencil-alt', enable: self.check_user_permission, diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js index e1019899a..07951d954 100644 --- a/web/pgadmin/browser/static/js/quick_search/trigger_search.js +++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js @@ -167,6 +167,9 @@ export function Search() { function handleClickOutside(event) { if (ref.current && !ref.current.contains(event.target)) { let input_element = document.getElementById('live-search-field'); + if(input_element == null){ + return; + } let input_value = input_element.value; if(input_value && input_value.length > 0){ toggleDropdownMenu(); diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 15b49cefc..9bb32f172 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -583,7 +583,7 @@ define([ icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, data: { - 'data_disabled': gettext('Please select any server from the browser tree to take Backup of global objects.'), + data_disabled: gettext('Please select any server from the browser tree to take Backup of global objects.'), }, }, { name: 'backup_server', @@ -595,7 +595,7 @@ define([ icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, data: { - 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + data_disabled: gettext('Please select any server from the browser tree to take Server Backup.'), }, }, { name: 'backup_global_ctx', @@ -608,7 +608,7 @@ define([ icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, data: { - 'data_disabled': gettext('Please select any database or schema or table from the browser tree to take Backup.'), + data_disabled: gettext('Please select any database or schema or table from the browser tree to take Backup.'), }, }, { name: 'backup_server_ctx', @@ -621,7 +621,7 @@ define([ icon: 'fa fa-save', enable: menuUtils.menuEnabledServer, data: { - 'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'), + data_disabled: gettext('Please select any server from the browser tree to take Server Backup.'), }, }, { name: 'backup_object', @@ -635,7 +635,7 @@ define([ null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes ), data: { - 'data_disabled': gettext('Please select any database or schema or table from the browser tree to take Backup.'), + data_disabled: gettext('Please select any database or schema or table from the browser tree to take Backup.'), }, }]; diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index 6db4c9922..adc6651bf 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -114,7 +114,7 @@ define('pgadmin.datagrid', [ label: gettext('Query Tool'), icon: 'pg-font-icon icon-query-tool', data:{ - 'data_disabled': gettext('Please select a database from the browser tree to access Query Tool.'), + data_disabled: gettext('Please select a database from the browser tree to access Query Tool.'), }, }]; diff --git a/web/pgadmin/tools/erd/static/js/erd_module.js b/web/pgadmin/tools/erd/static/js/erd_module.js index fd93b3125..3c1946aa4 100644 --- a/web/pgadmin/tools/erd/static/js/erd_module.js +++ b/web/pgadmin/tools/erd/static/js/erd_module.js @@ -40,6 +40,9 @@ export function initialize(gettext, url_for, $, _, pgAdmin, csrfToken, pgBrowser priority: 1, label: gettext('New ERD Project (Beta)'), enable: this.erdToolEnabled, + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + }, }]; pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index c7c7c459c..8e7a067f2 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -178,7 +178,7 @@ define([ null, pgBrowser.treeMenu, menuUtils.supportedNodes ), data: { - 'data_disabled': gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'), + data_disabled: gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'), }, }]; diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index d225183ce..fa3570ace 100644 --- a/web/pgadmin/tools/import_export/static/js/import_export.js +++ b/web/pgadmin/tools/import_export/static/js/import_export.js @@ -413,7 +413,7 @@ define([ null, pgBrowser.treeMenu, ['table'] ), data: { - 'data_disabled': gettext('Please select any table from the browser tree to Import/Export data.'), + data_disabled: gettext('Please select any table from the browser tree to Import/Export data.'), }, }]); }, diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index cf850c76d..e23d07d7b 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -168,6 +168,9 @@ define([ enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), + data: { + data_disabled: gettext('Please select any database from the browser tree to do Maintenance.'), + }, }]; // Add supported menus into the menus list @@ -185,7 +188,7 @@ define([ null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), data: { - 'data_disabled': gettext('Please select any database from the browser tree to do Maintenance.'), + data_disabled: gettext('Please select any database from the browser tree to do Maintenance.'), }, }); } diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index bdd250bb5..b0d43f9e5 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -399,7 +399,7 @@ define('tools.restore', [ null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes ), data: { - 'data_disabled': gettext('Please select any schema or table from the browser tree to Restore data.'), + data_disabled: gettext('Please select any schema or table from the browser tree to Restore data.'), }, }]; diff --git a/web/pgadmin/tools/search_objects/static/js/search_objects.js b/web/pgadmin/tools/search_objects/static/js/search_objects.js index ab898bf38..0f04e9c4a 100644 --- a/web/pgadmin/tools/search_objects/static/js/search_objects.js +++ b/web/pgadmin/tools/search_objects/static/js/search_objects.js @@ -37,7 +37,7 @@ define([ priority: 1, label: gettext('Search Objects...'), data: { - 'data_disabled': gettext('Please select a database from the browser tree to search the database objects.'), + data_disabled: gettext('Please select a database from the browser tree to search the database objects.'), }, }, { name: 'search_objects', [image/png] image001.png (68.4K, 4-image001.png) download | view image [image/png] image002.png (68.4K, 5-image002.png) download | view image [image/png] image003.png (68.4K, 6-image003.png) download | view image [image/png] image004.png (68.4K, 7-image004.png) download | view image [image/png] image005.png (68.4K, 8-image005.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-02-03 06:23 Murtuza Zabuawala <[email protected]> parent: Akshay Joshi <[email protected]> 1 sibling, 0 replies; 17+ messages in thread From: Murtuza Zabuawala @ 2021-02-03 06:23 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: Pramod Ahire <[email protected]>; Dave Page <[email protected]>; pgadmin-hackers Hello, On Tue, Feb 2, 2021 at 2:49 PM Akshay Joshi <[email protected]> wrote: > Thanks, patch applied with some string modifications. > Co-author name is missing from the commit message, The feature is implemented in two parts, Frontend: Pramod Backend: Murtuza (As mentioned by Pramod in the previous email) Thanks, Murtuza > > On Mon, Feb 1, 2021 at 1:32 PM Pramod Ahire <[email protected]> > wrote: > >> Hi Akshay, >> >> >> >> Please find the rebased patch as attached with this email. >> >> >> >> Please do let me know if need anything else. >> >> >> >> *Pramod Ahire* >> >> *Software Engineer* >> >> >> >> [image: >> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >> >> >> >> C: +91-020-66449600/601 >> >> D: +91-9028697679 >> >> *edbpostgres.com <http://edbpostgres.com>* >> >> >> >> >> >> *From: *Akshay Joshi <[email protected]> >> *Date: *Monday, 25 January 2021 at 3:53 PM >> *To: *Pramod Ahire <[email protected]> >> *Cc: *Dave Page <[email protected]>, pgadmin-hackers < >> [email protected]> >> *Subject: *Re: Quick search for menu items & help articles >> >> >> >> Hi Pramod >> >> >> >> The patch is not applied, maybe you need to rebase and send the patch >> again. >> >> >> >> On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire < >> [email protected]> wrote: >> >> Hi Team, >> >> >> >> Sorry for the inconvenience, kindly please discard previous email. >> >> >> >> Please review patch attached to this email. >> >> Changes as below : >> >> 1. Added complete quick search menu to help dropdown >> 2. Added message to each disabled menu item to describe why it can be >> disabled >> >> 3. Info icon design to menu items results if disabled >> 4. Standard/dark/high contrast theme supports >> >> NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” >> >> >> >> Thanks ! >> >> >> >> *Pramod Ahire* >> >> *Software Engineer* >> >> >> >> [image: >> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >> >> >> >> C: +91-020-66449600/601 >> >> D: +91-9028697679 >> >> *edbpostgres.com <http://edbpostgres.com>* >> >> >> >> >> >> *From: *Pramod Ahire <[email protected]> >> *Date: *Wednesday, 20 January 2021 at 4:19 PM >> *To: *Akshay Joshi <[email protected]>, Dave Page < >> [email protected]> >> *Cc: *pgadmin-hackers <[email protected]> >> *Subject: *Re: Quick search for menu items & help articles >> >> >> >> Hi Team, >> >> >> >> As per inputs from you all, made changes & attached patch with this email. >> >> >> >> Kindly please have a review & do let me know if missed anything. >> >> >> >> Thanks ! >> >> >> >> *Pramod Ahire* >> >> *Software Engineer* >> >> >> >> [image: >> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >> >> >> >> C: +91-020-66449600/601 >> >> D: +91-9028697679 >> >> *edbpostgres.com <http://edbpostgres.com>* >> >> >> >> >> >> *From: *Akshay Joshi <[email protected]> >> *Date: *Friday, 15 January 2021 at 3:34 PM >> *To: *Dave Page <[email protected]> >> *Cc: *Pramod Ahire <[email protected]>, pgadmin-hackers < >> [email protected]> >> *Subject: *Re: Quick search for menu items & help articles >> >> >> >> >> >> >> >> On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: >> >> >> >> >> >> On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi < >> [email protected]> wrote: >> >> Hi Dave >> >> >> >> On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: >> >> Hi >> >> >> >> On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < >> [email protected]> wrote: >> >> Hi Dave, >> >> >> >> Thank you for comments ! I had described points below. Please do let me >> know if anything missing. >> >> >> >> *From: *Dave Page <[email protected]> >> *Date: *Thursday, 14 January 2021 at 10:26 PM >> *To: *Pramod Ahire <[email protected]> >> *Cc: *pgadmin-hackers <[email protected]> >> *Subject: *Re: Quick search for menu items & help articles >> >> >> >> Hi >> >> >> >> On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < >> [email protected]> wrote: >> >> Hi Team, >> >> >> >> Please find the attached designs & patch that contains complete >> functionality except below to do for quick search. >> >> >> >> To Do: >> >> >> >> - Unit test cases are not that sufficient to cover complete code, but >> will be working in background to cover up those one >> - In pgadmin, for disabled menu items we need to add info that will >> describe why menu has disabled & how it will be enabled. Either another way >> to enable all of them & show respective reason in popup that menu is >> disabled for. >> >> >> >> Please do let me know if I missed anything or suggestion of yours. >> >> >> >> Looks very good. I haven't done an extensive code review/test, but two >> things spring to mind immediately: >> >> >> >> 1) I think the search box should be the top item on the Help menu. I do >> not think it should be on the far end of the menu bar, as it looks too much >> like it will search for data (think of search on a website). >> >> >> >> - As we are showing menu items as well in search results, it can be >> redundant for end user & increase duplications of menu items. Please advise >> your thoughts on this. >> >> >> >> The search box could be on the Help menu, with results shown in a >> sub-menu under it. >> >> >> >> Another more simple option would be to move the search icon to be >> directly after the Help menu (which would hint that it's related to the >> menus), but I'm also concerned about the number of items on the top menu - >> we're getting to the point that 'common' window sizes would show the >> hamburger menu by default. >> >> >> >> I personally feel we should not add the search box on the Help menu, >> it should be kept separate with a placeholder indicating what is it for. We >> can move the search icon after the Help menu. >> >> >> >> What is your reasoning? It is, after all, specifically designed to help >> people. >> >> >> >> OK, I have seen a couple of applications where the search box is >> inside the Help menu, agree with your point. >> >> >> >> >> >> >> >> 2) Do we need another loading icon? Surely there's one in the source tree >> already that we can use? >> >> >> >> - As we are loading help articles count in background, so I have added >> background loading icon to show near to count of results. Our existing icon >> is of blue & white circle combination, which will be more useful to show >> foreground loading. >> >> >> >> Hmm, OK. >> >> >> >> >> >> Please do let me know your valuable inputs on this. >> >> >> >> >> >> *Pramod Ahire* >> >> *Software Engineer* >> >> >> >> [image: >> https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] >> >> >> >> C: +91-020-66449600/601 >> >> D: +91-9028697679 >> >> *edbpostgres.com <http://edbpostgres.com>* >> >> >> >> >> >> >> >> >> >> >> >> >> -- >> >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EDB: http://www.enterprisedb.com >> >> >> >> >> -- >> >> *Thanks & Regards* >> >> *Akshay Joshi* >> >> *pgAdmin Hacker | Principal Software Architect* >> >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> >> >> >> >> -- >> >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EDB: http://www.enterprisedb.com >> >> >> >> >> -- >> >> *Thanks & Regards* >> >> *Akshay Joshi* >> >> *pgAdmin Hacker | Principal Software Architect* >> >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> >> >> >> >> -- >> >> *Thanks & Regards* >> >> *Akshay Joshi* >> >> *pgAdmin Hacker | Principal Software Architect* >> >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Principal Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image [image/png] image002.png (68.4K, 4-image002.png) download | view image [image/png] image003.png (68.4K, 5-image003.png) download | view image [image/png] image004.png (68.4K, 6-image004.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
* Re: Quick search for menu items & help articles @ 2021-02-03 07:29 Akshay Joshi <[email protected]> parent: Pramod Ahire <[email protected]> 0 siblings, 0 replies; 17+ messages in thread From: Akshay Joshi @ 2021-02-03 07:29 UTC (permalink / raw) To: Pramod Ahire <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers Thanks, patch applied. On Wed, Feb 3, 2021 at 11:27 AM Pramod Ahire <[email protected]> wrote: > Hi Akshay, > > > > As reported jasmine test case failure for quick search, had fixed issue & > attached here in email. > > > > Kindly also find below updates in the patch: > > 1. Jasmine test cases fixed > > 2. Added missing messages for disabled menu items > 3. Removed single quotes from ‘data_disabled’ -> data_disabled > > > > Please review & do let me know if missed anything. > > > > Thanks! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Tuesday, 2 February 2021 at 2:48 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *Dave Page <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Thanks, patch applied with some string modifications. > > > > On Mon, Feb 1, 2021 at 1:32 PM Pramod Ahire <[email protected]> > wrote: > > Hi Akshay, > > > > Please find the rebased patch as attached with this email. > > > > Please do let me know if need anything else. > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Monday, 25 January 2021 at 3:53 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *Dave Page <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi Pramod > > > > The patch is not applied, maybe you need to rebase and send the patch > again. > > > > On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Sorry for the inconvenience, kindly please discard previous email. > > > > Please review patch attached to this email. > > Changes as below : > > 1. Added complete quick search menu to help dropdown > 2. Added message to each disabled menu item to describe why it can be > disabled > > 3. Info icon design to menu items results if disabled > 4. Standard/dark/high contrast theme supports > > NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala” > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Pramod Ahire <[email protected]> > *Date: *Wednesday, 20 January 2021 at 4:19 PM > *To: *Akshay Joshi <[email protected]>, Dave Page < > [email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi Team, > > > > As per inputs from you all, made changes & attached patch with this email. > > > > Kindly please have a review & do let me know if missed anything. > > > > Thanks ! > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > *From: *Akshay Joshi <[email protected]> > *Date: *Friday, 15 January 2021 at 3:34 PM > *To: *Dave Page <[email protected]> > *Cc: *Pramod Ahire <[email protected]>, pgadmin-hackers < > [email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > > > > > On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote: > > > > > > On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi < > [email protected]> wrote: > > Hi Dave > > > > On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote: > > Hi > > > > On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire < > [email protected]> wrote: > > Hi Dave, > > > > Thank you for comments ! I had described points below. Please do let me > know if anything missing. > > > > *From: *Dave Page <[email protected]> > *Date: *Thursday, 14 January 2021 at 10:26 PM > *To: *Pramod Ahire <[email protected]> > *Cc: *pgadmin-hackers <[email protected]> > *Subject: *Re: Quick search for menu items & help articles > > > > Hi > > > > On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire < > [email protected]> wrote: > > Hi Team, > > > > Please find the attached designs & patch that contains complete > functionality except below to do for quick search. > > > > To Do: > > > > - Unit test cases are not that sufficient to cover complete code, but > will be working in background to cover up those one > - In pgadmin, for disabled menu items we need to add info that will > describe why menu has disabled & how it will be enabled. Either another way > to enable all of them & show respective reason in popup that menu is > disabled for. > > > > Please do let me know if I missed anything or suggestion of yours. > > > > Looks very good. I haven't done an extensive code review/test, but two > things spring to mind immediately: > > > > 1) I think the search box should be the top item on the Help menu. I do > not think it should be on the far end of the menu bar, as it looks too much > like it will search for data (think of search on a website). > > > > - As we are showing menu items as well in search results, it can be > redundant for end user & increase duplications of menu items. Please advise > your thoughts on this. > > > > The search box could be on the Help menu, with results shown in a sub-menu > under it. > > > > Another more simple option would be to move the search icon to be directly > after the Help menu (which would hint that it's related to the menus), but > I'm also concerned about the number of items on the top menu - we're > getting to the point that 'common' window sizes would show the hamburger > menu by default. > > > > I personally feel we should not add the search box on the Help menu, > it should be kept separate with a placeholder indicating what is it for. We > can move the search icon after the Help menu. > > > > What is your reasoning? It is, after all, specifically designed to help > people. > > > > OK, I have seen a couple of applications where the search box is > inside the Help menu, agree with your point. > > > > > > > > 2) Do we need another loading icon? Surely there's one in the source tree > already that we can use? > > > > - As we are loading help articles count in background, so I have added > background loading icon to show near to count of results. Our existing icon > is of blue & white circle combination, which will be more useful to show > foreground loading. > > > > Hmm, OK. > > > > > > Please do let me know your valuable inputs on this. > > > > > > *Pramod Ahire* > > *Software Engineer* > > > > [image: > https://lh4.googleusercontent.com/U1erEuyI_d0xEUA0CrKjwx9aWQ52HNCxc38dMsLP-ZrLgfVNrhsrNobxlmeOdb1kMP...] > > > > C: +91-020-66449600/601 > > D: +91-9028697679 > > *edbpostgres.com <http://edbpostgres.com>* > > > > > > > > > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EDB: http://www.enterprisedb.com > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > > > > > -- > > *Thanks & Regards* > > *Akshay Joshi* > > *pgAdmin Hacker | Principal Software Architect* > > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Principal Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* Attachments: [image/png] image001.png (68.4K, 3-image001.png) download | view image [image/png] image002.png (68.4K, 4-image002.png) download | view image [image/png] image003.png (68.4K, 5-image003.png) download | view image [image/png] image004.png (68.4K, 6-image004.png) download | view image [image/png] image005.png (68.4K, 7-image005.png) download | view image ^ permalink raw reply [nested|flat] 17+ messages in thread
end of thread, other threads:[~2021-02-03 07:29 UTC | newest] Thread overview: 17+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-01-14 11:24 Quick search for menu items & help articles Pramod Ahire <[email protected]> 2021-01-14 16:55 ` Dave Page <[email protected]> 2021-01-15 07:23 ` Pramod Ahire <[email protected]> 2021-01-15 09:16 ` Dave Page <[email protected]> 2021-01-15 09:43 ` Akshay Joshi <[email protected]> 2021-01-15 09:45 ` Dave Page <[email protected]> 2021-01-15 10:04 ` Akshay Joshi <[email protected]> 2021-01-20 10:49 ` Pramod Ahire <[email protected]> 2021-01-20 11:06 ` Pramod Ahire <[email protected]> 2021-01-25 10:23 ` Akshay Joshi <[email protected]> 2021-02-01 08:02 ` Pramod Ahire <[email protected]> 2021-02-02 09:18 ` Akshay Joshi <[email protected]> 2021-02-03 05:56 ` Pramod Ahire <[email protected]> 2021-02-03 07:29 ` Akshay Joshi <[email protected]> 2021-02-03 06:23 ` Murtuza Zabuawala <[email protected]> 2021-01-15 06:44 ` Akshay Joshi <[email protected]> 2021-01-15 07:24 ` Pramod Ahire <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox