///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import React, {useRef,useState, useEffect} from 'react'; import { styled } from '@mui/material/styles'; import { CircularProgress, Typography } from '@mui/material'; import {useDelayDebounce} from 'sources/custom_hooks'; import {onlineHelpSearch} from './online_help'; import {menuSearch} from './menuitems_help'; import gettext from 'sources/gettext'; import PropTypes from 'prop-types'; import { InputText } from '../components/FormComponents'; import EmptyPanelMessage from '../components/EmptyPanelMessage'; const StyledDiv = styled('div')(({theme}) => ({ '& .QuickSearch-loaderRoot': { display: 'flex', alignItems: 'center', padding: '8px', justifyContent: 'center', '& .QuickSearch-loader': { height: '25px !important', width: '25px !important', marginRight: '8px', } }, '& .QuickSearch-helpGroup': { backgroundColor: theme.palette.grey[400], padding: '6px', fontSize: '0.85em', fontWeight: 600, display: 'flex', alignItems: 'center', }, '& .QuickSearch-searchItem': { display: 'flex', flexDirection: 'column', padding: '4px 8px', textDecoration: 'none', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, '&:hover, &:focus': { backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastText, outline: 'none !important', }, '&.disabled': { opacity: 0.6, pointerEvents: 'none', } }, '& .QuickSearch-showAll': { marginLeft: 'auto', color: 'inherit', textDecoration: 'none' }, })); function SearchLoader({loading=false}) { if(loading) { return (
{gettext('Searching...')}
); } return <>; } SearchLoader.propTypes = { loading: PropTypes.bool }; function HelpArticleContents({isHelpLoading, isMenuLoading, helpSearchResult}) { return (isHelpLoading && !(isMenuLoading??true)) ? ( <>
 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 ? Show all   : '' }
) : <>; } HelpArticleContents.propTypes = { helpSearchResult: PropTypes.object, isHelpLoading: PropTypes.bool, isMenuLoading: PropTypes.bool }; export default function QuickSearch({closeModal}) { const wrapperRef = useRef(null); const [searchTerm, setSearchTerm] = useState(''); const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false); 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(() => { if(menuSearchResult.fetched){ setIsMenuLoading(false); } if(helpSearchResult.fetched){ setIsHelpLoading(false); } }, [menuSearchResult, helpSearchResult]); const initSearch = (param) => { if(param.length < 3) { return; } 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 = []; items.forEach((i) => { menuItemsHtmlElement.push(
{ closeModal(); i.callback(); } }> {i.label} {i.path}
); }); return menuItemsHtmlElement; } }; const onInputValueChange = (value) => { let pooling = window.pooling; if(pooling){ window.clearInterval(pooling); } resetSearchState(); setSearchTerm(value); if(value.length >= 3){ setIsMenuLoading(true); setIsHelpLoading(true); setIsShowMinLengthMsg(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'); if(input_element == null){ return; } let input_value = input_element.value; if(input_value && input_value.length > 0){ toggleDropdownMenu(); } } } // Bind the event listener document.addEventListener('mousedown', handleClickOutside); return () => { // Unbind the event listener on clean up document.removeEventListener('mousedown', handleClickOutside); }; }, [ref]); }; useOutsideAlerter(wrapperRef); return (
{/* no need */}}>
,