import { Async } from '@uifabric/utilities/lib/Async' ; import findIndex from 'lodash/findIndex' ; import orderBy from 'lodash/orderBy' ; import { Dropdown , IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown' ; import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox' ; import { Spinner , SpinnerSize } from 'office-ui-fabric-react/lib/Spinner' ; import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling' ; import { ISelectableOption , SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/utilities/selectableOption/SelectableOption.types' ; import * as React from 'react' ; import * as telemetry from '../../common/telemetry' ; import { toRelativeUrl } from '../../common/utilities/GeneralHelper' ; import { getAllSites , getHubSites } from '../../services/SPSitesService' ; import { ISite , ISitePickerProps } from './ISitePicker' ; const styles = mergeStyleSets ( { loadingSpinnerContainer : { width : '100%' , textAlign : 'center' , marginTop : '8px' } , searchBox : { margin : '4px 0' } , siteOption : { display : 'flex' , whiteSpace : 'nowrap' , alignItems : 'center' } , siteOptionCheckbox : { display : 'inline-block' , marginRight : '4px' } , siteOptionContent : { display : 'flex' , flexDirection : 'column' , minWidth : '0' , padding : '4px 0' } , siteOptionTitle : { lineHeight : '18px' , overflow : 'hidden' , textOverflow : 'ellipsis' } , siteOptionUrl : { fontSize : '12px' , lineHeight : '14px' , fontWeight : '300' , overflow : 'hidden' , textOverflow : 'ellipsis' } } ) ; const async = new Async ( ) ; export const SitePicker = ( props ) => { const { label , disabled , context , initialSites , multiSelect , mode , limitToCurrentSiteCollection , allowSearch , orderBy : propOrderBy , isDesc , onChange , placeholder , searchPlaceholder , deferredSearchTime , className , selectedSites , trimDuplicates , additionalQuery } = props ; const [ isLoading , setIsLoading ] = React . useState < boolean > ( ) ; const [ sites , setSites ] = React . useState < ISite [ ] > ( ) ; const [ allSites , setAllSites ] = React . useState < ISite [ ] > ( ) ; const [ filteredSites , setFilteredSites ] = React . useState < ISite [ ] > ( ) ; const [ searchQuery , setSearchQuery ] = React . useState < string > ( ) ; const onSearchChange = React . useCallback ( ( e , newSearchQuery ) => { if ( ! allSites ) { return ; } const loweredNewSearchQuery = newSearchQuery . toLowerCase ( ) ; const newFilteredSites = allSites . filter ( s => s . title && s . title . toLowerCase ( ) . indexOf ( loweredNewSearchQuery ) !== - 1 ) ; setSearchQuery ( newSearchQuery ) ; setFilteredSites ( newFilteredSites ) ; } , [ allSites ] ) ; const onSelectionChange = React . useCallback ( ( e , item , index ) => { let newSelectedSites = [ ] ; if ( multiSelect !== false ) { newSelectedSites = sites ? [ ... sites ] : [ ] ; const existingIndex = findIndex ( newSelectedSites , s => s . url === item . key ) ; if ( existingIndex >= 0 ) { newSelectedSites . splice ( existingIndex , 1 ) ; } else { newSelectedSites . push ( { ... item . data ! } ) ; } } else { newSelectedSites = [ { ... item . data ! } ] ; } if ( onChange ) { onChange ( newSelectedSites ) ; } setSites ( newSelectedSites ) ; } , [ sites , multiSelect , onChange ] ) ; const getOptions = React . useCallback ( ( ) : IDropdownOption [ ] => { if ( ! allSites ) { return [ { key : 'spinner' , text : '' , itemType : SelectableOptionMenuItemType . Header } ] ; } const result = [ ] ; if ( allowSearch !== false ) { result . push ( { key : 'search' , text : '' , itemType : SelectableOptionMenuItemType . Header } ) ; } const selectedSitesIds = sites ? sites . map ( s => s . url ! ) : [ ] ; if ( filteredSites ) { filteredSites . forEach ( s => { result . push ( { key : s . url , text : s . title , data : s , selected : selectedSitesIds . indexOf ( s . url ) !== - 1 } ) ; } ) ; } return result ; } , [ allowSearch , sites , filteredSites , allSites ] ) ; const onRenderOption = ( option ? , defaultRender ? ) : JSX . Element | null => { if ( ! props ) { return null ; } if ( option . itemType === SelectableOptionMenuItemType . Header ) { if ( option . key === 'search' ) { return < SearchBox placeholder = { searchPlaceholder } value = { searchQuery } onChange = { async . debounce ( onSearchChange , deferredSearchTime || 200 ) } className = { styles . searchBox } / > ; } else if ( option . key === 'spinner' ) { setIsLoading ( true ) ; return < div className = { styles . loadingSpinnerContainer } > < Spinner size = { SpinnerSize . medium } /> < /div>; } } return < div className = { styles . siteOption } > < div className = { styles . siteOptionContent } > < span className = { styles . siteOptionTitle } > { option . text } < /span> < span className = { styles . siteOptionUrl } > { toRelativeUrl ( option . data ! . url ) } < /span> < /div> < /div>; } ; React . useEffect ( ( ) => { telemetry . track ( 'ReactSitePicker' ) ; } , [ ] ) ; React . useEffect ( ( ) => { setSites ( selectedSites ) ; if ( ! allSites ) { setIsLoading ( true ) ; } } , [ selectedSites ] ) ; React . useEffect ( ( ) => { if ( ! initialSites ) { return ; } setSites ( osites => { if ( ! osites ) { return initialSites ; } return sites ; } ) ; if ( ! allSites ) { setIsLoading ( true ) ; } } , [ initialSites , allSites ] ) ; React . useEffect ( ( ) => { if ( ! context || ! isLoading ) { return ; } setSearchQuery ( '' ) ; setFilteredSites ( [ ] ) ; let promise ; if ( mode === 'hub' ) { promise = getHubSites ( context ) ; } else { promise = getAllSites ( context , mode !== 'site' , limitToCurrentSiteCollection , trimDuplicates === true , additionalQuery ) ; } promise . then ( newSites => { const copy = orderBy ( newSites , [ propOrderBy || 'title' ] , [ isDesc ? 'desc' : 'asc' ] ) ; setAllSites ( copy ) ; setIsLoading ( false ) ; } ) ; } , [ context , isLoading , mode , limitToCurrentSiteCollection ] ) ; React . useEffect ( ( ) => { setAllSites ( s => { if ( ! s ) { return s ; } const copy = orderBy ( s , [ propOrderBy || 'title' ] , [ isDesc ? 'desc' : 'asc' ] ) ; return copy ; } ) ; } , [ propOrderBy , isDesc ] ) ; React . useEffect ( ( ) => { if ( ! allSites ) { return ; } setFilteredSites ( [ ... allSites ] ) ; } , [ allSites ] ) ; return ( < > < Dropdown label = { label } placeholder = { placeholder } options = { getOptions ( ) } selectedKey = { multiSelect === false && ! ! sites && ! ! sites [ 0 ] ? sites [ 0 ] . url : undefined } selectedKeys = { multiSelect !== false && ! ! sites ? sites . map ( s => s . url ) : undefined } disabled = { disabled } multiSelect = { multiSelect !== false } onRenderOption = { onRenderOption } onChange = { onSelectionChange } notifyOnReselect = { true } className = { className } / > < />); } ;