import * as React from 'react' ; import { IWebSearchTabProps } from './IWebSearchTabProps' ; import { IWebSearchTabState } from './IWebSearchTabState' ; import { ISearchSuggestion , ImageSize , ImageAspect , ImageLicense , DEFAULT_SUGGESTIONS , MAX_ROW_HEIGHT , ROWS_PER_PAGE } from './WebSearchTab.types' ; import { PrimaryButton , DefaultButton } from 'office-ui-fabric-react/lib/components/Button' ; import { Label } from 'office-ui-fabric-react/lib/Label' ; import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox' ; import { Check } from 'office-ui-fabric-react/lib/Check' ; import { Dropdown , IDropdownProps , IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown' ; import { Image , ImageFit } from 'office-ui-fabric-react/lib/Image' ; import { Link } from 'office-ui-fabric-react/lib/Link' ; import { FocusZone } from 'office-ui-fabric-react/lib/FocusZone' ; import { List } from 'office-ui-fabric-react/lib/List' ; import { IRectangle } from 'office-ui-fabric-react/lib/Utilities' ; import { Selection , SelectionMode , SelectionZone } from 'office-ui-fabric-react/lib/Selection' ; import { MessageBar } from 'office-ui-fabric-react/lib/MessageBar' ; import { css } from 'office-ui-fabric-react/lib/Utilities' ; import { IFilePickerResult } from '../FilePicker.types' ; import { GeneralHelper } from '../../../common/utilities/GeneralHelper' ; import { ISearchResult , BingQuerySearchParams } from '../../../services/FilesSearchService.types' ; import styles from './WebSearchTab.module.scss' ; import * as strings from 'ControlStrings' ; /**
 * Renders search suggestions and performs seach queries
 */ export default class WebSearchTab extends React . Component < IWebSearchTabProps , IWebSearchTabState > { private _columnCount : number ; private _columnWidth : number ; private _rowHeight : number ; private _selection : Selection ; private _listElem : List = undefined ; constructor ( props ) { super ( props ) ; this . _selection = new Selection ( { selectionMode : SelectionMode . single , onSelectionChanged : this . _onSelectionChanged } ) ; this . state = { isLoading : false , results : undefined , filePickerResult : null } ; } /**
     * Render the tab
     */ public render ( ) { const { query , results } = this . state ; return ( < div className = { styles . tabContainer } > < div className = { styles . tabHeaderContainer } > < h2 className = { styles . tabHeader } > { strings . WebSearchLinkLabel } < /h2> < /div> { this . props . bingSearchService && this . _renderSearchBox ( ) } < div className = { css ( styles . tab , styles . tabOffset ) } > { ! query && this . _renderSearchSuggestions ( ) } { query && results && this . _renderSearchResults ( ) } < /div> < div className = { styles . actionButtonsContainer } > { this . state . results && this . state . license === 'Any' && < MessageBar > { strings . CreativeCommonsMessage } < /MessageBar>} < Label className = { styles . copyrightLabel } > { strings . CopyrightWarning } & nbsp ; & nbsp ; < Link target = '_blank' href = { strings . CopyrightUrl } > { strings . LearnMoreLink } < /Link> < /Label> < div className = { styles . actionButtons } > < PrimaryButton disabled = { ! this . state . filePickerResult } className = { styles . actionButton } onClick = { ( ) => this . _handleSave ( ) } > { strings . OpenButtonLabel } < /PrimaryButton> < DefaultButton onClick = { ( ) => this . _handleClose ( ) } className = { styles . actionButton } > { strings . CancelButtonLabel } < /DefaultButton> < /div> < /div> < /div>); } private _onSelectionChanged = ( ) => { const selectedItems = this . _selection . getSelection ( ) ; const filePickerResult = this . state . filePickerResult ; let selectedFileResult = null ; if ( selectedItems && selectedItems . length > 0 ) { const selectedItem = selectedItems [ 0 ] as ISearchResult ; const selectedUrl = selectedItem . contentUrl . replace ( 'http://' , 'https://' ) ; selectedFileResult = { fileAbsoluteUrl : selectedUrl , fileName : GeneralHelper . getFileNameFromUrl ( selectedUrl ) , fileNameWithoutExtension : GeneralHelper . getFileNameWithoutExtension ( selectedUrl ) , downloadFileContent : ( ) => { return this . props . bingSearchService . downloadBingContent ( selectedUrl , GeneralHelper . getFileNameFromUrl ( selectedUrl ) ) ; } } ; } if ( filePickerResult && selectedFileResult && filePickerResult . fileAbsoluteUrl === selectedFileResult . fileAbsoluteUrl ) { this . _selection . setAllSelected ( false ) ; selectedFileResult = null ; } this . setState ( { filePickerResult : selectedFileResult } ) ; if ( this . _listElem ) { this . _listElem . forceUpdate ( ) ; } } ; private _clearSearch = ( ) => { this . setState ( { query : undefined , results : undefined , filePickerResult : undefined } ) ; } ; private _renderSearchResults = ( ) : JSX . Element => { const { results } = this . state ; if ( results === undefined || results . length < 1 ) { return < Label className = { styles . noResultLabel } > { strings . NoResultsBadEnglish } < /Label>; } return ( < FocusZone > < SelectionZone selection = { this . _selection } onItemInvoked = { ( item ) => this . _selection . setKeySelected ( item . key , true , true ) } > < List ref = { this . _linkElement } className = { styles . bingGrildList } items = { this . state . results } getItemCountForPage = { this . _getItemCountForPage } getPageHeight = { this . _getPageHeight } renderedWindowsAhead = { 4 } onRenderCell = { this . _onRenderSearchResultsCell } / > < /SelectionZone> < /FocusZone>); } ; private _onRenderSearchResultsCell = ( item , index ) : JSX . Element => { const { query } = this . state ; let isSelected = false ; if ( this . _selection && index !== undefined ) { isSelected = this . _selection . isIndexSelected ( index ) ; } const ratio = item . width / item . height ; let thumbnailHeight = Math . min ( this . _rowHeight , item . height ) ; let thumbnailWidth = thumbnailHeight * ratio ; const searchResultAltText = strings . SearchResultAlt . replace ( '{0}' , query ) ; return ( < div className = { styles . bingGridListCell } style = { { width : 100 / this . _columnCount + '%' } } > < div aria - label = { searchResultAltText } className = { css ( styles . bingTile , isSelected ? styles . isSelected : undefined ) } data - is - focusable = { true } data - selection - index = { index } style = { { width : `${ thumbnailWidth }px` , height : `${ thumbnailHeight }px` } } > < div className = { styles . bingTileContent } data - selection - invoke = { true } > < Image src = { item . thumbnailUrl } className = { styles . bingTileThumbnail } alt = { searchResultAltText } width = { thumbnailWidth } height = { thumbnailHeight } / > < div className = { styles . bingTileFrame } > < /div> < div className = { styles . bingTileCheckCircle } role = 'checkbox' aria - checked = { isSelected } data - item - index = { index } data - selection - toggle = { true } data - automationid = 'CheckCircle' > < Check checked = { isSelected } /> < /div> < div className = { styles . bingTileNamePlate } > < Link href = { item . contentUrl } target = '_blank' aria - label = { strings . SearchResultAriaLabel } > { item . displayUrl } < /Link> < /div> < /div> < /div> < /div>); } ; private _renderSearchSuggestions = ( ) : JSX . Element => { const suggestions = this . props . suggestions !== undefined ? this . props . suggestions : DEFAULT_SUGGESTIONS ; return ( < FocusZone > < List className = { styles . filePickerFolderCardGrid } items = { suggestions } getItemCountForPage = { this . _getItemCountForPage } getPageHeight = { this . _getPageHeight } renderedWindowsAhead = { 4 } onRenderCell = { this . _onRenderSuggestionCell } / > < /FocusZone>); } ; private _getSearchResults = async ( ) => { if ( this . state . query === undefined || ! this . props . bingSearchService ) { return ; } this . setState ( { filePickerResult : null , isLoading : true } ) ; const searchParams = { aspect : this . state . aspect , size : this . state . size , license : this . state . license , query : this . state . query } ; const searchResults = await this . props . bingSearchService . executeBingSearch ( searchParams ) ; if ( searchResults ) { this . _selection . setItems ( searchResults , true ) ; } this . setState ( { isLoading : false , results : searchResults } ) ; } ; private _getItemCountForPage = ( itemIndex , surfaceRect ) : number => { if ( itemIndex === 0 ) { this . _columnCount = Math . ceil ( surfaceRect . width / MAX_ROW_HEIGHT ) ; this . _columnWidth = Math . floor ( surfaceRect . width / this . _columnCount ) ; this . _rowHeight = this . _columnWidth ; } return this . _columnCount * ROWS_PER_PAGE ; } ; private _getPageHeight = ( ) : number => { return this . _rowHeight * ROWS_PER_PAGE ; } ; private _onRenderSuggestionCell = ( item , index ) : JSX . Element => { return ( < div className = { styles . filePickerFolderCardTile } data - is - focusable = { true } style = { { width : 100 / this . _columnCount + '%' } } > < div className = { styles . filePickerFolderCardSizer } > < div className = { styles . filePickerFolderCardPadder } > < Image src = { item . backgroundUrl } className = { styles . filePickerFolderCardImage } imageFit = { ImageFit . cover } / > < DefaultButton className = { styles . filePickerFolderCardLabel } onClick = { ( _event ) => this . _handleSearch ( item . topic ) } > { item . topic } < /DefaultButton> < /div> < /div> < /div>); } ; private _renderSearchBox = ( ) : JSX . Element => { const { query } = this . state ; const hasQuery = query !== undefined ; const license = this . state . license ? this . state . license : 'All' ; return ( < div className = { styles . searchBoxContainer } > < div className = { styles . searchBoxMedium } > < div className = { styles . searchBox } > < SearchBox placeholder = { strings . SearchBoxPlaceholder } value = { query } onSearch = { newQuery => this . _handleSearch ( newQuery ) } / > < /div> < /div> < Label > { strings . PoweredByBing } < /Label> { hasQuery && < div className = { styles . dropdownContainer } > < Dropdown className = { styles . filterDropdown } onRenderPlaceHolder = { ( props ) => this . _renderFilterPlaceholder ( props ) } selectedKey = { this . state . size } options = { [ { key : 'All' , text : strings . SizeOptionAll } , { key : 'Small' , text : strings . SizeOptionSmall } , { key : 'Medium' , text : strings . SizeOptionMedium } , { key : 'Large' , text : strings . SizeOptionLarge } , { key : 'Wallpaper' , text : strings . SizeOptionExtraLarge } ] } onChanged = { ( option , index ? ) => this . _handleChangeSize ( option ) } /> < Dropdown className = { styles . filterDropdown } onRenderPlaceHolder = { ( props ) => this . _renderFilterPlaceholder ( props ) } selectedKey = { this . state . aspect } options = { [ { key : 'All' , text : strings . LayoutOptionAll } , { key : 'Square' , text : strings . LayoutOptionSquare } , { key : 'Wide' , text : strings . LayoutOptionWide } , { key : 'Tall' , text : strings . LayoutOptionTall } , ] } onChanged = { ( option , index ? ) => this . _handleChangeLayout ( option ) } /> < Dropdown className = { styles . filterDropdown } onRenderPlaceHolder = { ( props ) => this . _renderFilterPlaceholder ( props ) } selectedKey = { license } options = { [ { key : 'All' , text : strings . LicenseOptionAll } , { key : 'Any' , text : strings . LicenseOptionAny } ] } onChanged = { ( option , index ? ) => this . _handleChangeLicense ( option ) } /> < /div>} < /div>); } ; private _handleChangeSize = ( option ) => { this . setState ( { size : option . key as ImageSize } , ( ) => this . _getSearchResults ( ) ) ; } ; private _handleChangeLayout = ( option ) => { this . setState ( { aspect : option . key as ImageAspect } , ( ) => this . _getSearchResults ( ) ) ; } ; private _handleChangeLicense = ( option ) => { this . setState ( { license : option . key as ImageLicense } , ( ) => this . _getSearchResults ( ) ) ; } ; private _renderFilterPlaceholder = ( props ) : JSX . Element => { return < span > Pick the value < /span>; } ; private _handleSearch = ( newQuery ? ) => { this . setState ( { query : newQuery } , ( ) => this . _getSearchResults ( ) ) ; } ; private _handleClose = ( ) => { this . props . onClose ( ) ; } ; private _handleSave = ( ) => { this . props . onSave ( [ this . state . filePickerResult ] ) ; } ; private _linkElement = ( e ) => { this . _listElem = e ; } ; }