import { BaseComponentContext } from '@microsoft/sp-component-base' ; import { IFile , FilesQueryResult , ILibrary } from "./FileBrowserService.types" ; import { SPHttpClient } from "@microsoft/sp-http" ; import { GeneralHelper } from "../common/utilities/GeneralHelper" ; export class FileBrowserService { protected itemsToDownloadCount : number ; protected context : BaseComponentContext ; protected driveAccessToken : string ; protected mediaBaseUrl : string ; protected callerStack : string ; constructor ( context , itemsToDownloadCount = 100 ) { this . context = context ; this . itemsToDownloadCount = itemsToDownloadCount ; this . driveAccessToken = null ; } param listUrl param folderPath param acceptedFilesExtensions public getListItems = async ( listUrl , folderPath , acceptedFilesExtensions ? , nextPageQueryStringParams ? , sortBy ? , isDesc ? ) : Promise < FilesQueryResult > => { let filesQueryResult = { items : [ ] , nextHref : null } ; try { let restApi = `${ this . context . pageContext . web . absoluteUrl }/_api/web/GetList('${ listUrl }')/RenderListDataAsStream` ; if ( nextPageQueryStringParams ) { restApi += `${ nextPageQueryStringParams }` ; folderPath = null ; } filesQueryResult = await this . _getListDataAsStream ( restApi , folderPath , acceptedFilesExtensions , sortBy , isDesc ) ; } catch ( error ) { filesQueryResult . items = null ; console . error ( error . message ) ; } return filesQueryResult ; } ; /**
     * Provides the URL for file preview.
     */ public getFileThumbnailUrl = ( file , thumbnailWidth , thumbnailHeight ) : string => { const thumbnailUrl = `${ this . mediaBaseUrl }/transform/thumbnail?provider=spo&inputFormat=${ file . fileType }&cs=${ this . callerStack }&docid=${ file . spItemUrl }&${ this . driveAccessToken }&width=${ thumbnailWidth }&height=${ thumbnailHeight }` ; return thumbnailUrl ; } ; /**
     * Gets document and media libraries from the site
     */ public getSiteMediaLibraries = async ( includePageLibraries = false ) => { try { const absoluteUrl = this . context . pageContext . web . absoluteUrl ; const restApi = `${ absoluteUrl }/_api/SP.Web.GetDocumentAndMediaLibraries?webFullUrl='${ encodeURIComponent ( absoluteUrl ) }'&includePageLibraries='${ includePageLibraries }'` ; const mediaLibrariesResult = await this . context . spHttpClient . get ( restApi , SPHttpClient . configurations . v1 ) ; if ( ! mediaLibrariesResult || ! mediaLibrariesResult . ok ) { throw new Error ( `Something went wrong when executing request. Status='${ mediaLibrariesResult . status }'` ) ; } const libResults = await mediaLibrariesResult . json ( ) ; if ( ! libResults || ! libResults . value ) { throw new Error ( `Cannot read data from the results.` ) ; } const result = libResults . value . map ( ( libItem ) => { return this . parseLibItem ( libItem , absoluteUrl ) ; } ) ; return result ; } catch ( error ) { console . error ( `[FileBrowserService.getSiteMediaLibraries]: Err='${ error . message }'` ) ; return null ; } } ; /**
     * Gets document and media libraries from the site
     */ public getLibraryNameByInternalName = async ( internalName ) : Promise < string > => { try { const absoluteUrl = this . context . pageContext . web . absoluteUrl ; const restApi = `${ absoluteUrl }/_api/web/GetFolderByServerRelativeUrl('${ internalName }')/Properties?$select=vti_x005f_listtitle` ; const libraryResult = await this . context . spHttpClient . get ( restApi , SPHttpClient . configurations . v1 ) ; if ( ! libraryResult || ! libraryResult . ok ) { throw new Error ( `Something went wrong when executing request. Status='${ libraryResult . status }'` ) ; } const libResults = await libraryResult . json ( ) ; if ( ! libResults || ! libResults . vti_x005f_listtitle ) { throw new Error ( `Cannot read data from the results.` ) ; } return libResults . vti_x005f_listtitle != internalName && libResults . vti_x005f_listtitle || "" ; } catch ( error ) { console . error ( `[FileBrowserService.getSiteLibraryNameByInternalName]: Err='${ error . message }'` ) ; return null ; } } ; /**
     * Downloads document content from SP location.
     */ public downloadSPFileContent = async ( absoluteFileUrl , fileName ) : Promise < File > => { try { const fileDownloadResult = await this . context . spHttpClient . get ( absoluteFileUrl , SPHttpClient . configurations . v1 ) ; if ( ! fileDownloadResult || ! fileDownloadResult . ok ) { throw new Error ( `Something went wrong when downloading the file. Status='${ fileDownloadResult . status }'` ) ; } const blob = await fileDownloadResult . blob ( ) ; return GeneralHelper . getFileFromBlob ( blob , fileName ) ; } catch ( err ) { console . error ( `[FileBrowserService.fetchFileContent] Err='${ err . message }'` ) ; return null ; } } ; param filePropertyName returns public getSPFieldNameForFileProperty ( filePropertyName ) { let fieldName = '' ; switch ( filePropertyName ) { case 'fileIcon' : fieldName = 'DocIcon' ; break ; case 'serverRelativeUrl' : fieldName = 'FileRef' ; break ; case 'modified' : case 'modifiedDate' : fieldName = 'Modified' ; break ; case 'fileSize' : fieldName = 'File_x0020_Size' ; break ; case 'fileType' : fieldName = 'File_x0020_Type' ; break ; case 'modifiedBy' : fieldName = 'Editor' ; break ; } return fieldName ; } param restApi param folderPath param acceptedFilesExtensions protected _getListDataAsStream = async ( restApi , folderPath , acceptedFilesExtensions ? , sortBy ? , isDesc ? ) : Promise < FilesQueryResult > => { let filesQueryResult = { items : [ ] , nextHref : null } ; try { const body = { parameters : { AllowMultipleValueFilterForTaxonomyFields : true , RenderOptions : 1 | 2 | 4 | 1024 | 4096 | 8192 , ViewXml : this . getFilesCamlQueryViewXml ( acceptedFilesExtensions , sortBy || 'FileLeafRef' , ! ! isDesc ) } } ; if ( folderPath ) { body . parameters [ "FolderServerRelativeUrl" ] = folderPath ; } const data = await this . context . spHttpClient . fetch ( restApi , SPHttpClient . configurations . v1 , { method : "POST" , body : JSON . stringify ( body ) } ) ; if ( ! data || ! data . ok ) { throw new Error ( `[FileBrowser._getListItems]: Something went wrong when executing request. Status='${ data . statusMessage }'` ) ; } const filesResult = await data . json ( ) ; if ( ! filesResult || ! filesResult . ListData || ! filesResult . ListData . Row ) { throw new Error ( `[FileBrowser._getListItems]: No data is available. Status='${ data . statusMessage }'` ) ; } this . processResponse ( filesResult ) ; const items = filesResult . ListData . Row . map ( fileItem => this . parseFileItem ( fileItem ) ) ; filesQueryResult = { items : items , nextHref : filesResult . ListData . NextHref } ; } catch ( error ) { filesQueryResult . items = null ; console . error ( error . message ) ; } return filesQueryResult ; } ; param accepts protected getFileTypeFilter ( accepts ) { let fileFilter = "" ; if ( accepts && accepts . length > 0 ) { fileFilter = "<Values>" ; accepts . forEach ( ( fileType , index ) => { fileType = fileType . replace ( "." , "" ) ; if ( index >= 0 ) { fileFilter = fileFilter + `<Value Type="Text">${ fileType }</Value>` ; } } ) ; fileFilter = fileFilter + "</Values>" ; } return fileFilter ; } /**
     * Generates Files CamlQuery ViewXml
     */ protected getFilesCamlQueryViewXml = ( accepts , sortBy , isDesc ) => { const fileFilter = this . getFileTypeFilter ( accepts ) ; let queryCondition = fileFilter && fileFilter != "" ? `<Query>
        <Where>
          <Or>
            <And>
              <Eq>
                <FieldRef Name="FSObjType" />
                <Value Type="Text">1</Value>
              </Eq>
              <Eq>
                <FieldRef Name="SortBehavior" />
                <Value Type="Text">1</Value>
              </Eq>
            </And>
            <In>
              <FieldRef Name="File_x0020_Type" />
              ${ fileFilter }
            </In>
          </Or>
        </Where>
        <OrderBy><FieldRef Name="${ sortBy }" Ascending="${ isDesc ? 'False' : 'True' }" /></OrderBy>
      </Query>` : `<Query><OrderBy><FieldRef Name="${ sortBy }" Ascending="${ isDesc ? 'False' : 'True' }" /></OrderBy></Query>` ; const viewXml = `<View>
                      ${ queryCondition }
                      <ViewFields>
                        <FieldRef Name="DocIcon"/>
                        <FieldRef Name="LinkFilename"/>
                        <FieldRef Name="Modified"/>
                        <FieldRef Name="Editor"/>
                        <FieldRef Name="FileSizeDisplay"/>
                        <FieldRef Name="SharedWith"/>
                        <FieldRef Name="MediaServiceFastMetadata"/>
                        <FieldRef Name="MediaServiceOCR"/>
                        <FieldRef Name="_ip_UnifiedCompliancePolicyUIAction"/>
                        <FieldRef Name="ItemChildCount"/>
                        <FieldRef Name="FolderChildCount"/>
                        <FieldRef Name="SMTotalFileCount"/>
                        <FieldRef Name="SMTotalSize"/>
                      </ViewFields>
                      <RowLimit Paged="TRUE">${ this . itemsToDownloadCount }</RowLimit>
                    </View>` ; return viewXml ; } ; /**
     * Converts REST call results to IFile
     */ protected parseFileItem = ( fileItem ) : IFile => { const modifiedFriendly = fileItem [ "Modified.FriendlyDisplay" ] ; const modifiedParts = modifiedFriendly ! . split ( '|' ) ; let modified = fileItem . Modified ; if ( modifiedParts . length === 2 ) { modified = modifiedParts [ 1 ] ; } const file = { name : fileItem . FileLeafRef , fileIcon : fileItem . DocIcon , serverRelativeUrl : fileItem . FileRef , modified : modified , modifiedDate : new Date ( fileItem . Modified ) , fileSize : fileItem . File_x0020_Size , fileType : fileItem . File_x0020_Type , modifiedBy : fileItem . Editor ! [ 0 ] ! . title , isFolder : fileItem . FSObjType === "1" , absoluteUrl : this . buildAbsoluteUrl ( fileItem . FileRef ) , supportsThumbnail : true , spItemUrl : fileItem [ ".spItemUrl" ] } ; return file ; } ; protected parseLibItem = ( libItem , webUrl ) : ILibrary => { const library = { title : libItem . Title , absoluteUrl : libItem . AbsoluteUrl , serverRelativeUrl : libItem . ServerRelativeUrl , webRelativeUrl : libItem . AbsoluteUrl . replace ( webUrl , '' ) } ; return library ; } ; /**
     * Creates an absolute URL
     */ protected buildAbsoluteUrl = ( relativeUrl ) => { const siteUrl = GeneralHelper . getAbsoluteDomainUrl ( this . context . pageContext . web . absoluteUrl ) ; return `${ siteUrl }${ relativeUrl . indexOf ( '/' ) === 0 ? '' : '/' }${ relativeUrl }` ; } ; protected processResponse = ( fileResponse ) : void => { this . mediaBaseUrl = fileResponse . ListSchema [ ".mediaBaseUrl" ] ; this . callerStack = fileResponse . ListSchema [ ".callerStack" ] ; this . driveAccessToken = fileResponse . ListSchema [ ".driveAccessToken" ] ; } ; }