// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import React from "react"; import { compose } from "redux"; import { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles, Tooltip, InputAdornment, Input } from "@material-ui/core"; import SearchIcon from "@material-ui/icons/Search"; import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; import { ArvadosTheme } from "common/custom-theme"; import { SearchView } from "store/search-bar/search-bar-reducer"; import { SearchBarBasicView, SearchBarBasicViewDataProps, SearchBarBasicViewActionProps } from "views-components/search-bar/search-bar-basic-view"; import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps, SearchBarAutocompleteViewActionProps, } from "views-components/search-bar/search-bar-autocomplete-view"; import { SearchBarAdvancedView, SearchBarAdvancedViewDataProps, SearchBarAdvancedViewActionProps, } from "views-components/search-bar/search-bar-advanced-view"; import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "common/codes"; import { debounce } from "debounce"; import { Vocabulary } from "models/vocabulary"; import { connectVocabulary } from "../resource-properties-form/property-field-common"; import { Session } from "models/session"; type CssRules = "container" | "containerSearchViewOpened" | "input" | "view"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => { return { container: { position: "relative", width: "100%", borderRadius: theme.spacing.unit / 2, zIndex: theme.zIndex.modal, }, containerSearchViewOpened: { position: "relative", width: "100%", borderRadius: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px 0 0`, zIndex: theme.zIndex.modal, }, input: { border: "none", padding: `0`, }, view: { position: "absolute", width: "100%", zIndex: 1, }, }; }; export type SearchBarDataProps = SearchBarViewDataProps & SearchBarAutocompleteViewDataProps & SearchBarAdvancedViewDataProps & SearchBarBasicViewDataProps; interface SearchBarViewDataProps { searchValue: string; currentView: string; isPopoverOpen: boolean; debounce?: number; vocabulary?: Vocabulary; sessions: Session[]; } export type SearchBarActionProps = SearchBarViewActionProps & SearchBarAutocompleteViewActionProps & SearchBarAdvancedViewActionProps & SearchBarBasicViewActionProps; interface SearchBarViewActionProps { onChange: (event: React.ChangeEvent) => void; onSubmit: (event: React.FormEvent) => void; onSetView: (currentView: string) => void; closeView: () => void; openSearchView: () => void; loadRecentQueries: () => string[]; moveUp: () => void; moveDown: () => void; setAdvancedDataFromSearchValue: (search: string, vocabulary?: Vocabulary) => void; searchSingleCluster: (session: Session, searchValue: string) => any; } type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles; const handleKeyDown = (e: React.KeyboardEvent, props: SearchBarViewProps) => { if (e.keyCode === KEY_CODE_DOWN) { e.preventDefault(); if (!props.isPopoverOpen) { props.onSetView(SearchView.AUTOCOMPLETE); props.openSearchView(); } else { props.moveDown(); } } else if (e.keyCode === KEY_CODE_UP) { e.preventDefault(); props.moveUp(); } else if (e.keyCode === KEY_CODE_ESC) { e.preventDefault(); props.closeView(); } else if (e.keyCode === KEY_ENTER) { if (props.currentView === SearchView.BASIC) { e.preventDefault(); props.onSearch(props.selectedItem.query); } else if (props.currentView === SearchView.AUTOCOMPLETE) { if (props.selectedItem.id !== props.searchValue) { e.preventDefault(); props.navigateTo(props.selectedItem.id); } } } }; const handleInputClick = (e: React.MouseEvent, props: SearchBarViewProps) => { if (props.searchValue) { props.onSetView(SearchView.AUTOCOMPLETE); } else { props.onSetView(SearchView.BASIC); } props.openSearchView(); }; const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => { e.stopPropagation(); if (props.isPopoverOpen && props.currentView === SearchView.ADVANCED) { props.closeView(); } else { props.setAdvancedDataFromSearchValue(props.searchValue, props.vocabulary); props.onSetView(SearchView.ADVANCED); } }; export const SearchBarView = compose( connectVocabulary, withStyles(styles) )( class extends React.Component { state={ loggedInSessions: [], } debouncedSearch = debounce(() => { this.props.onSearch(this.props.searchValue); }, 1000); handleChange = (event: React.ChangeEvent) => { this.debouncedSearch(); this.props.onChange(event); }; handleSubmit = (event: React.FormEvent) => { this.debouncedSearch.clear(); this.props.onSubmit(event); }; componentDidMount(): void { this.setState({ loggedInSessions: this.props.sessions.filter((ss) => ss.loggedIn && ss.userIsActive)}); } componentDidUpdate( prevProps: Readonly, prevState: Readonly<{loggedInSessions: Session[]}>, snapshot?: any ): void { if (prevProps.sessions !== this.props.sessions) { this.setState({ loggedInSessions: this.props.sessions.filter((ss) => ss.loggedIn)}); } //if a new session is logged in after a search is started, search the new cluster and append those to the results if(prevState.loggedInSessions.length !== this.state.loggedInSessions.length){ const newLogin = this.state.loggedInSessions.filter((ss) => !prevState.loggedInSessions.includes(ss)); this.props.searchSingleCluster(newLogin[0], this.props.searchValue); } } componentWillUnmount() { this.debouncedSearch.clear(); } render() { const { children, ...props } = this.props; const { classes, isPopoverOpen } = this.props; return ( <> {isPopoverOpen && }
handleInputClick(e, props)} onKeyDown={e => handleKeyDown(e, props)} startAdornment={ } endAdornment={ handleDropdownClick(e, props)}> } />
{isPopoverOpen && getView({ ...props })}
); } } ); const getView = (props: SearchBarViewProps) => { switch (props.currentView) { case SearchView.AUTOCOMPLETE: return ( ); case SearchView.ADVANCED: return ( ); default: return ( ); } }; const Backdrop = withStyles<"backdrop">(theme => ({ backdrop: { position: "fixed", top: 0, right: 0, bottom: 0, left: 0, zIndex: theme.zIndex.modal, }, }))(({ classes, ...props }: WithStyles<"backdrop"> & React.HTMLProps) => (
));