import * as React from 'react' ; import { IFile , FilesQueryResult } from '../../../../services/FileBrowserService.types' ; import { GeneralHelper , sortDate , sortString } from '../../../../common/utilities/GeneralHelper' ; import { LoadingState } from './IFileBrowserState' ; import { TilesList } from '../TilesList/TilesList' ; import { IFilePickerResult } from '../../FilePicker.types' ; import { IFileBrowserProps } from './IFileBrowserProps' ; import { IFileBrowserState } from './IFileBrowserState' ; import { ViewType } from './FileBrowser.types' ; import { Spinner } from 'office-ui-fabric-react/lib/Spinner' ; import { DetailsList , DetailsListLayoutMode , Selection , SelectionMode , IColumn , IDetailsRowProps , DetailsRow } from 'office-ui-fabric-react/lib/DetailsList' ; import { CommandBar , ICommandBarItemProps } from 'office-ui-fabric-react/lib/CommandBar' ; import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu' ; import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane' ; import styles from './FileBrowser.module.scss' ; import * as strings from 'ControlStrings' ; import { FileTypeIcon } from '../../../fileTypeIcon/FileTypeIcon' ; import { IconType , ImageSize } from '../../../fileTypeIcon' ; const LAYOUT_STORAGE_KEY = 'comparerSiteFilesLayout' ; export class FileBrowser extends React . Component < IFileBrowserProps , IFileBrowserState > { private _selection : Selection ; constructor ( props ) { super ( props ) ; const lastLayout = localStorage ? localStorage . getItem ( LAYOUT_STORAGE_KEY ) as ViewType : 'list' as ViewType ; const columns = [ { key : 'column1' , name : 'Type' , ariaLabel : strings . TypeAriaLabel , iconName : 'Page' , isIconOnly : true , fieldName : 'docIcon' , headerClassName : styles . iconColumnHeader , minWidth : 20 , maxWidth : 20 , onColumnClick : this . _onColumnClick , onRender : ( item ) => { if ( item . isFolder ) { return < div className = { styles . fileTypeIcon } > < img src = { strings . FolderIconUrl } className = { styles . fileTypeIconIcon } alt = { strings . FolderAltText } title = { strings . FolderAltText } / > < /div>; } return < FileTypeIcon type = { IconType . image } path = { item . serverRelativeUrl } size = { ImageSize . normal } / > ; } } , { key : 'column2' , name : strings . NameField , fieldName : 'name' , minWidth : 210 , isRowHeader : true , isResizable : true , isSorted : true , isSortedDescending : false , sortAscendingAriaLabel : strings . SortedAscending , sortDescendingAriaLabel : strings . SortedDescending , onColumnClick : this . _onColumnClick , data : 'string' , isPadded : true , onRender : ( item ) => { if ( item . isFolder ) { return < span className = { styles . folderItem } onClick = { ( _event ) => this . _handleOpenFolder ( item ) } > { item . name } < /span>; } else { return < span className = { styles . fileItem } > { item . name } < /span>; } } , } , { key : 'column3' , name : strings . ModifiedField , fieldName : 'modified' , minWidth : 120 , isResizable : true , onColumnClick : this . _onColumnClick , data : 'date' , onRender : ( item ) => { return < span > { item . modified } < /span>; } , isPadded : true } , { key : 'column4' , name : strings . ModifiedByField , fieldName : 'modifiedBy' , minWidth : 120 , isResizable : true , data : 'string' , onColumnClick : this . _onColumnClick , onRender : ( item ) => { return < span > { item . modifiedBy } < /span>; } , isPadded : true } , { key : 'column5' , name : strings . FileSizeField , fieldName : 'fileSize' , minWidth : 70 , maxWidth : 90 , isResizable : true , data : 'number' , onColumnClick : this . _onColumnClick , onRender : ( item ) => { return < span > { item . fileSize ? GeneralHelper . formatBytes ( item . fileSize , 1 ) : undefined } < /span>; } } ] ; this . _selection = new Selection ( { selectionMode : SelectionMode . single } ) ; this . state = { columns : columns , items : [ ] , nextPageQueryString : null , loadingState : LoadingState . loading , selectedView : lastLayout , filePickerResult : null } ; } public componentDidUpdate ( prevProps , prevState ) { if ( this . props . folderPath !== prevProps . folderPath ) { this . _selection . setAllSelected ( false ) ; this . _getListItems ( ) ; } } public componentDidMount ( ) { this . _getListItems ( ) ; } public render ( ) { return ( < div > { ( this . state . items && this . state . items . length > 0 && this . state . loadingState != LoadingState . loading ) && < div > < div className = { styles . itemPickerTopBar } > < CommandBar items = { this . _getToolbarItems ( ) } farItems = { this . getFarItems ( ) } / > < /div> < div className = { styles . scrollablePaneWrapper } > < ScrollablePane > { this . state . selectedView !== 'tiles' ? ( < DetailsList items = { this . state . items } compact = { this . state . selectedView === 'compact' } columns = { this . state . columns } selectionMode = { SelectionMode . single } setKey = "set" layoutMode = { DetailsListLayoutMode . justified } isHeaderVisible = { true } selection = { this . _selection } onActiveItemChanged = { ( item , index , ev ) => this . _handleItemInvoked ( item ) } selectionPreservedOnEmptyClick = { true } enterModalSelectionOnTouch = { true } onRenderRow = { this . _onRenderRow } onRenderMissingItem = { ( ) => { this . _loadNextDataRequest ( ) ; return null ; } } />) : ( < TilesList fileBrowserService = { this . props . fileBrowserService } filePickerResult = { this . state . filePickerResult } selection = { this . _selection } items = { this . state . items } onFolderOpen = { this . _handleOpenFolder } onFileSelected = { this . _itemSelectionChanged } onNextPageDataRequest = { this . _loadNextDataRequest } / > ) } < /ScrollablePane> < /div> < /div>} { ( this . state . loadingState === LoadingState . idle && ( ! this . state . items || this . state . items . length <= 0 ) ) && this . _renderEmptyFolder ( ) } { this . state . loadingState != LoadingState . idle && < Spinner label = { strings . Loading } />} < /div>); } private _loadNextDataRequest = async ( ) => { if ( this . state . loadingState == LoadingState . idle ) { await this . _getListItems ( true ) ; } } ; private _renderEmptyFolder = ( ) : JSX . Element => { return ( < div className = { styles . emptyFolder } > < div className = { styles . emptyFolderImage } > < img className = { styles . emptyFolderImageTag } src = { strings . OneDriveEmptyFolderIconUrl } alt = { strings . OneDriveEmptyFolderAlt } / > < /div> < div role = "alert" > < div className = { styles . emptyFolderTitle } > { strings . OneDriveEmptyFolderTitle } < /div> < div className = { styles . emptyFolderSubText } > < span className = { styles . emptyFolderPc } > { strings . OneDriveEmptyFolderDescription } < /span> { } { } < /div> < /div> < /div>); } ; private _onRenderRow = ( props ) : JSX . Element => { const fileItem = props . item ; return < DetailsRow { ... props } className = { fileItem . isFolder ? styles . folderRow : styles . fileRow } / > ; } ; private _getToolbarItems = ( ) : ICommandBarItemProps [ ] => { return [ ] ; } ; private getFarItems = ( ) : ICommandBarItemProps [ ] => { const { selectedView } = this . state ; let viewIconName = undefined ; let viewName = undefined ; switch ( this . state . selectedView ) { case 'list' : viewIconName = 'List' ; viewName = strings . ListLayoutList ; break ; case 'compact' : viewIconName = 'AlignLeft' ; viewName = strings . ListLayoutCompact ; break ; default : viewIconName = 'GridViewMedium' ; viewName = strings . ListLayoutTile ; } const farItems = [ { key : 'listOptions' , className : styles . commandBarNoChevron , title : strings . ListOptionsTitle , ariaLabel : strings . ListOptionsAlt . replace ( '{0}' , viewName ) , iconProps : { iconName : viewIconName } , iconOnly : true , subMenuProps : { items : [ { key : 'list' , name : strings . ListLayoutList , iconProps : { iconName : 'List' } , canCheck : true , checked : this . state . selectedView === 'list' , ariaLabel : strings . ListLayoutAriaLabel . replace ( '{0}' , strings . ListLayoutList ) . replace ( '{1}' , selectedView === 'list' ? strings . Selected : undefined ) , title : strings . ListLayoutListDescrition , onClick : ( _ev ? , item ? ) => this . _handleSwitchLayout ( item ) } , { key : 'compact' , name : strings . ListLayoutCompact , iconProps : { iconName : 'AlignLeft' } , canCheck : true , checked : this . state . selectedView === 'compact' , ariaLabel : strings . ListLayoutAriaLabel . replace ( '{0}' , strings . ListLayoutCompact ) . replace ( '{1}' , selectedView === 'compact' ? strings . Selected : undefined ) , title : strings . ListLayoutCompactDescription , onClick : ( _ev ? , item ? ) => this . _handleSwitchLayout ( item ) } , { key : 'tiles' , name : 'Tiles' , iconProps : { iconName : 'GridViewMedium' } , canCheck : true , checked : this . state . selectedView === 'tiles' , ariaLabel : strings . ListLayoutAriaLabel . replace ( '{0}' , strings . ListLayoutTile ) . replace ( '{1}' , selectedView === 'tiles' ? strings . Selected : undefined ) , title : strings . ListLayoutTileDescription , onClick : ( _ev ? , item ? ) => this . _handleSwitchLayout ( item ) } ] } } ] ; return farItems ; } ; private _handleSwitchLayout = ( item ? ) => { if ( item ) { if ( localStorage ) { localStorage . setItem ( LAYOUT_STORAGE_KEY , item . key ) ; } this . setState ( { selectedView : item . key as ViewType } ) ; } } ; private _onColumnClick = ( event , column ) : void => { const { columns } = this . state ; let { items } = this . state ; let isSortedDescending = column . isSortedDescending ; if ( column . isSorted ) { isSortedDescending = ! isSortedDescending ; } const updatedColumns = columns ! . map ( col => { col . isSorted = col . key === column . key ; if ( col . isSorted ) { col . isSortedDescending = isSortedDescending ; } return col ; } ) ; if ( ! this . state . nextPageQueryString ) { items = items ! . concat ( [ ] ) . sort ( ( a , b ) => { if ( a . isFolder && ! b . isFolder ) { return 1 ; } else if ( ! a . isFolder && b . isFolder ) { return - 1 ; } let firstValue = a [ column . fieldName ] || '' ; let secondValue = b [ column . fieldName ] || '' ; if ( column . data === 'string' ) { return sortString ( firstValue , secondValue , isSortedDescending ) ; } else if ( column . data === 'date' ) { return sortDate ( firstValue , secondValue , isSortedDescending ) ; } else if ( column . data === 'number' ) { firstValue = parseFloat ( firstValue ) ; secondValue = parseFloat ( secondValue ) ; } return isSortedDescending ? secondValue - firstValue : firstValue - secondValue ; } ) ; this . setState ( { items : items , columns : updatedColumns } ) ; } else { this . setState ( { items : [ ] , columns : updatedColumns } , ( ) => { this . _getListItems ( false ) ; } ) ; } } ; private _handleOpenFolder = ( item ) => { this . _selection . setAllSelected ( false ) ; this . setState ( { loadingState : LoadingState . loading , filePickerResult : undefined } , ( ) => { this . props . onOpenFolder ( item ) ; } ) ; } ; private _itemSelectionChanged = ( item ? ) => { let selectedItem = null ; if ( item && this . state . filePickerResult && item . absoluteUrl == this . state . filePickerResult . fileAbsoluteUrl ) { this . _selection . setAllSelected ( false ) ; selectedItem = null ; } else if ( item ) { const selectedItemIndex = this . state . items . indexOf ( item ) ; this . _selection . selectToIndex ( selectedItemIndex ) ; selectedItem = item ; } let filePickerResult = null ; if ( selectedItem && ! selectedItem . isFolder ) { filePickerResult = { fileAbsoluteUrl : selectedItem . absoluteUrl , fileName : GeneralHelper . getFileNameFromUrl ( selectedItem . name ) , fileNameWithoutExtension : GeneralHelper . getFileNameWithoutExtension ( selectedItem . name ) , spItemUrl : selectedItem . spItemUrl , downloadFileContent : null } ; } this . props . onChange ( filePickerResult ) ; this . setState ( { filePickerResult } ) ; } ; private _handleItemInvoked = ( item ) => { if ( item . isFolder ) { this . _handleOpenFolder ( item ) ; } else { this . _itemSelectionChanged ( item ) ; } } ; private async _getListItems ( concatenateResults = false ) { const { libraryUrl , folderPath , accepts , fileBrowserService } = this . props ; let { items , nextPageQueryString } = this . state ; let filesQueryResult = { items : [ ] , nextHref : null } ; const loadingState = concatenateResults ? LoadingState . loadingNextPage : LoadingState . loading ; nextPageQueryString = concatenateResults ? nextPageQueryString : null ; try { this . setState ( { loadingState , nextPageQueryString } ) ; let sortField = undefined ; let isDesc = undefined ; const sortByCol = this . state . columns ! . filter ( c => c . isSorted ) [ 0 ] ; if ( sortByCol ) { sortField = fileBrowserService . getSPFieldNameForFileProperty ( sortByCol . fieldName ) ; isDesc = ! ! sortByCol . isSortedDescending ; } filesQueryResult = await this . props . fileBrowserService . getListItems ( libraryUrl , folderPath , accepts , nextPageQueryString , sortField , isDesc ) ; } catch ( error ) { filesQueryResult . items = null ; console . error ( error . message ) ; } finally { if ( concatenateResults && items && items . length > 0 && items . length [ items . length - 1 ] == null ) { items . splice ( items . length - 1 , 1 ) ; } const newItems = concatenateResults ? items . concat ( filesQueryResult . items ) : filesQueryResult . items ; if ( filesQueryResult . nextHref != null ) { newItems . push ( null ) ; } if ( ! concatenateResults ) { this . _selection . setAllSelected ( false ) ; } this . setState ( { items : newItems , nextPageQueryString : filesQueryResult . nextHref , loadingState : LoadingState . idle } ) ; } } private _onClientSort ( column ) { } }