diff --git a/src/controls/filePicker/SiteFilePickerTab/SiteFilePickerTab.tsx b/src/controls/filePicker/SiteFilePickerTab/SiteFilePickerTab.tsx index 03bb03897..ad6cfca96 100644 --- a/src/controls/filePicker/SiteFilePickerTab/SiteFilePickerTab.tsx +++ b/src/controls/filePicker/SiteFilePickerTab/SiteFilePickerTab.tsx @@ -28,7 +28,7 @@ export default class SiteFilePickerTab extends React.Component { this.onBreadcrumpItemClick(itm); }; + breadcrumbSiteNode.onClick = (ev, itm) => { this.onBreadcrumbItemClick(itm); }; const breadcrumbItems: FilePickerBreadcrumbItem[] = [breadcrumbSiteNode]; @@ -59,100 +59,6 @@ export default class SiteFilePickerTab extends React.Component { this.onBreadcrumpItemClick(itm); } - }); - - if (folderServRelPath !== libraryServRelUrl) { - let folderLibRelPath = folderWebRelPath.substring(libInternalName.length + 2); - if (webServRelUrl === "/") { - folderLibRelPath = folderWebRelPath.substring(libInternalName.length + 1); - } - - let breadcrumbFolderServRelPath = libraryServRelUrl; - - const crumbs: FilePickerBreadcrumbItem[] = folderLibRelPath.split("/").map((currFolderName => { - breadcrumbFolderServRelPath += `/${currFolderName}`; - return { - isCurrentItem: false, - text: currFolderName, - key: urlCombine(tenantUrl, breadcrumbFolderServRelPath), - folderData: { - name: currFolderName, - absoluteUrl: urlCombine(tenantUrl, breadcrumbFolderServRelPath), - serverRelativeUrl: breadcrumbFolderServRelPath, - }, - onClick: (ev, itm) => { this.onBreadcrumpItemClick(itm); } - }; - })); - - folderBreadcrumbs.push(...crumbs); - } - return folderBreadcrumbs; - } - public componentDidMount(): void { this._defaultLibraryNamePromise.then(docLibName => { if (docLibName) { @@ -208,9 +114,9 @@ export default class SiteFilePickerTab extends React.Component { + private onBreadcrumbItemClick = (node: FilePickerBreadcrumbItem): void => { let { breadcrumbItems } = this.state; let breadcrumbClickedItemIndx = 0; // Site node clicked @@ -282,7 +188,7 @@ export default class SiteFilePickerTab extends React.Component { this.onBreadcrumpItemClick(itm); } + onClick: (ev, itm) => { this.onBreadcrumbItemClick(itm); } }; breadcrumbItems.push(breadcrumbNode); } @@ -308,7 +214,7 @@ export default class SiteFilePickerTab extends React.Component { this.onBreadcrumpItemClick(itm); } + onClick: (ev, itm) => { this.onBreadcrumbItemClick(itm); } }; breadcrumbItems.push(breadcrumbNode); } @@ -319,4 +225,115 @@ export default class SiteFilePickerTab extends React.Component { this.onBreadcrumbItemClick(itm); } + }); + + if (folderServRelPath !== libraryServRelUrl) { + let folderLibRelPath = folderWebRelPath.substring(libInternalName.length + 2); + if (webServRelUrl === "/") { + folderLibRelPath = folderWebRelPath.substring(libInternalName.length + 1); + } + + let breadcrumbFolderServRelPath = libraryServRelUrl; + + const crumbs: FilePickerBreadcrumbItem[] = folderLibRelPath.split("/").map((currFolderName => { + breadcrumbFolderServRelPath += `/${currFolderName}`; + return { + isCurrentItem: false, + text: currFolderName, + key: urlCombine(tenantUrl, breadcrumbFolderServRelPath), + folderData: { + name: currFolderName, + absoluteUrl: urlCombine(tenantUrl, breadcrumbFolderServRelPath), + serverRelativeUrl: breadcrumbFolderServRelPath, + }, + onClick: (ev, itm) => { this.onBreadcrumbItemClick(itm); } + }; + })); + + folderBreadcrumbs.push(...crumbs); + } + return folderBreadcrumbs; + } } diff --git a/src/services/FileBrowserService.ts b/src/services/FileBrowserService.ts index 8b8fc6e92..7613bd9d2 100644 --- a/src/services/FileBrowserService.ts +++ b/src/services/FileBrowserService.ts @@ -3,15 +3,34 @@ import { IFile, FilesQueryResult, ILibrary } from "./FileBrowserService.types"; import { SPHttpClient } from "@microsoft/sp-http"; import { GeneralHelper } from "../common/utilities/GeneralHelper"; +/** + * File Browser Service + */ export class FileBrowserService { + // Number of items to download protected itemsToDownloadCount: number; + + // Component context protected context: BaseComponentContext; + + // Site absolute URL protected siteAbsoluteUrl: string; + // Drive access token (additional file info) protected driveAccessToken: string; + + // Media base URL (additional file info) protected mediaBaseUrl: string; + + // Caller stack (additional file info) protected callerStack: string; + /** + * Constructor + * @param context Component context + * @param itemsToDownloadCount Number of items to download + * @param siteAbsoluteUrl Site absolute URL + */ constructor(context: BaseComponentContext, itemsToDownloadCount: number = 100, siteAbsoluteUrl?: string) { this.context = context; this.siteAbsoluteUrl = siteAbsoluteUrl || context.pageContext.web.absoluteUrl; @@ -23,8 +42,11 @@ export class FileBrowserService { /** * Gets files from current sites library * @param listUrl web-relative url of the list - * @param folderPath - * @param acceptedFilesExtensions + * @param folderPath Folder path to get items from + * @param acceptedFilesExtensions File extensions to filter the results + * @param nextPageQueryStringParams Query string parameters to get the next page of results + * @param sortBy Field to sort by + * @param isDesc Sort in descending order */ public getListItems = async (listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string, sortBy?: string, isDesc?: boolean): Promise => { let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; @@ -49,6 +71,10 @@ export class FileBrowserService { /** * Provides the URL for file preview. + * @param file File to get thumbnail URL + * @param thumbnailWidth Thumbnail width + * @param thumbnailHeight Thumbnail height + * @returns Thumbnail URL with the specified dimensions */ public getFileThumbnailUrl = (file: IFile, thumbnailWidth: number, thumbnailHeight: number): string => { const thumbnailUrl = `${this.mediaBaseUrl}/transform/thumbnail?provider=spo&inputFormat=${file.fileType}&cs=${this.callerStack}&docid=${file.spItemUrl}&${this.driveAccessToken}&width=${thumbnailWidth}&height=${thumbnailHeight}`; @@ -58,6 +84,8 @@ export class FileBrowserService { /** * Gets document and media libraries from the site + * @param includePageLibraries Include page libraries (default `false`) + * @returns Media libraries information */ public getSiteMediaLibraries = async (includePageLibraries: boolean = false): Promise => { try { @@ -83,6 +111,8 @@ export class FileBrowserService { /** * Gets document and media libraries from the site + * @param internalName Internal name of the library + * @returns Library name */ public getLibraryNameByInternalName = async (internalName: string): Promise => { try { @@ -107,6 +137,9 @@ export class FileBrowserService { /** * Downloads document content from SP location. + * @param absoluteFileUrl Absolute URL of the file + * @param fileName Name of the file + * @returns File content */ public downloadSPFileContent = async (absoluteFileUrl: string, fileName: string): Promise => { try { @@ -158,24 +191,9 @@ export class FileBrowserService { } /** - * Gets the Title of the current Web - * @returns SharePoint Site Title + * Gets the Title and Id of the current Web + * @returns SharePoint Site Title and Id */ - public getSiteTitle = async (): Promise => { - const restApi = `${this.siteAbsoluteUrl}/_api/web?$select=Title`; - const webResult = await this.context.spHttpClient.get(restApi, SPHttpClient.configurations.v1); - - if (!webResult || !webResult.ok) { - throw new Error(`Something went wrong when executing request. Status='${webResult.status}'`); - } - if (!webResult || !webResult) { - throw new Error(`Cannot read data from the results.`); - } - const webJson = await webResult.json(); - return webJson.Title; - - } - public getSiteTitleAndId = async (): Promise<{ title: string, id: string }> => { const restApi = `${this.siteAbsoluteUrl}/_api/web?$select=Title,Id`; const webResult = await this.context.spHttpClient.get(restApi, SPHttpClient.configurations.v1); @@ -193,9 +211,12 @@ export class FileBrowserService { /** * Executes query to load files with possible extension filtering - * @param restApi - * @param folderPath - * @param acceptedFilesExtensions + * @param restApi REST API URL + * @param folderPath Folder path to get items from + * @param acceptedFilesExtensions File extensions to filter the results + * @param sortBy Field to sort by + * @param isDesc Sort in descending order + * @returns Files query result */ protected _getListDataAsStream = async (restApi: string, folderPath: string, acceptedFilesExtensions?: string[], sortBy?: string, isDesc?: boolean): Promise => { let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; @@ -243,7 +264,8 @@ export class FileBrowserService { /** * Generates CamlQuery files filter. - * @param accepts + * @param accepts File extensions to filter the results + * @returns CamlQuery filter */ protected getFileTypeFilter(accepts: string[]): string { let fileFilter: string = ""; @@ -264,6 +286,10 @@ export class FileBrowserService { /** * Generates Files CamlQuery ViewXml + * @param accepts File extensions to filter the results + * @param sortBy Field to sort by + * @param isDesc Sort in descending order + * @returns CamlQuery ViewXml */ protected getFilesCamlQueryViewXml = (accepts: string[], sortBy: string, isDesc: boolean): string => { const fileFilter: string = this.getFileTypeFilter(accepts); @@ -316,6 +342,8 @@ export class FileBrowserService { /** * Converts REST call results to IFile + * @param fileItem File item from REST call + * @returns File information */ protected parseFileItem = (fileItem: any): IFile => { // eslint-disable-line @typescript-eslint/no-explicit-any const modifiedFriendly: string = fileItem["Modified.FriendlyDisplay"]; @@ -348,6 +376,12 @@ export class FileBrowserService { return file; } + /** + * Converts REST call results to ILibrary + * @param libItem Library item from REST call + * @param webUrl Web URL + * @returns Library information + */ protected parseLibItem = (libItem: any, webUrl: string): ILibrary => { // eslint-disable-line @typescript-eslint/no-explicit-any const library: ILibrary = { title: libItem.Title, @@ -361,12 +395,18 @@ export class FileBrowserService { /** * Creates an absolute URL + * @param relativeUrl Relative URL + * @returns Absolute URL */ protected buildAbsoluteUrl = (relativeUrl: string): string => { const siteUrl: string = GeneralHelper.getAbsoluteDomainUrl(this.siteAbsoluteUrl); return `${siteUrl}${relativeUrl.indexOf('/') === 0 ? '' : '/'}${relativeUrl}`; } + /** + * Processes the response from the REST call to get additional information for the requested file + * @param fileResponse REST call response + */ protected processResponse = (fileResponse: any): void => { // eslint-disable-line @typescript-eslint/no-explicit-any // Extract media base URL this.mediaBaseUrl = fileResponse.ListSchema[".mediaBaseUrl"]; diff --git a/src/services/OrgAssetsService.ts b/src/services/OrgAssetsService.ts index b57ab1ed4..0d0695412 100644 --- a/src/services/OrgAssetsService.ts +++ b/src/services/OrgAssetsService.ts @@ -3,29 +3,35 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { SPHttpClient } from "@microsoft/sp-http"; import { ILibrary, FilesQueryResult } from "./FileBrowserService.types"; +/** + * OrgAssetsService class + */ export class OrgAssetsService extends FileBrowserService { + // Site organization assets library server relative URL private _orgAssetsLibraryServerRelativeSiteUrl: string = null; + /** + * Constructor + * @param context Component context + * @param itemsToDownloadCount Items to download count + */ constructor(context: BaseComponentContext, itemsToDownloadCount?: number) { super(context, itemsToDownloadCount); } - public getListItems = async (listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string): Promise => { + /** + * Gets files from current sites library + * @param _listUrl Unused parameter (not used in this implementation) + * @param folderPath Folder path to get items from + * @param acceptedFilesExtensions File extensions to filter the results + * @param nextPageQueryStringParams Query string parameters to get the next page of results + * @returns Items in the specified folder + */ + public getListItems = async (_listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string): Promise => { let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; try { - - // Retrieve Lib path from folder path - const isRootSite = this.context.pageContext.site.serverRelativeUrl === '/'; - - if (!isRootSite) { - if (folderPath.charAt(0) !== '/') { - folderPath = `/${folderPath}`; - } - - } else { - if (folderPath.charAt(0) === '/') { - folderPath = folderPath.substring(1); - } + if (folderPath.charAt(0) !== '/') { + folderPath = `/${folderPath}`; } // Remove all the rest of the folder path @@ -33,7 +39,7 @@ export class OrgAssetsService extends FileBrowserService { libName = libName.split('/')[0]; // Build absolute library URL - const libFullUrl = this.buildAbsoluteUrl(!isRootSite ? `${this._orgAssetsLibraryServerRelativeSiteUrl}/${libName}` : `${this._orgAssetsLibraryServerRelativeSiteUrl}${libName}`); + const libFullUrl = this.buildAbsoluteUrl(`${this._orgAssetsLibraryServerRelativeSiteUrl}/${libName}`); let queryStringParams: string = ""; // Do not pass FolderServerRelativeUrl as query parameter @@ -57,6 +63,11 @@ export class OrgAssetsService extends FileBrowserService { return filesQueryResult; } + /** + * Gets document and media libraries from the site + * @param includePageLibraries Unused parameter (not used in this implementation) + * @returns Document and media libraries from the site + */ public getSiteMediaLibraries = async (includePageLibraries: boolean = false): Promise => { try { const restApi = `${this.context.pageContext.web.absoluteUrl}/_api/SP.Publishing.SitePageService.FilePickerTabOptions`; @@ -79,6 +90,11 @@ export class OrgAssetsService extends FileBrowserService { } } + /** + * Parses the organisation assets library item + * @param libItem Library item to parse + * @returns Organisation assets library + */ private _parseOrgAssetsLibraryItem = (libItem: any): ILibrary => { // eslint-disable-line @typescript-eslint/no-explicit-any const orgAssetsLibrary: ILibrary = { absoluteUrl: this.buildAbsoluteUrl(libItem.LibraryUrl.DecodedUrl), diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index c4a7793e6..6a4687b65 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -199,6 +199,7 @@ import { FieldPicker } from "../../../FieldPicker"; import { IPersonaProps, Toggle } from "@fluentui/react"; import { ListItemComments } from "../../../ListItemComments"; import { ViewPicker } from "../../../controls/viewPicker"; +import { GeneralHelper } from "../../../Utilities"; @@ -736,13 +737,15 @@ export default class ControlsTest extends React.Component { - this.setState({ filePickerResult: filePickerResult }); if (filePickerResult && filePickerResult.length > 0) { for (var i = 0; i < filePickerResult.length; i++) { const item = filePickerResult[i]; const fileResultContent = await item.downloadFileContent(); console.log(fileResultContent); + filePickerResult[i].fileSize = fileResultContent.size; } + + this.setState({ filePickerResult: filePickerResult }); } } @@ -1854,7 +1857,7 @@ export default class ControlsTest extends React.Component
- File size: {this.state.filePickerResult[0].fileSize} + File size: {GeneralHelper.formatBytes(this.state.filePickerResult[0].fileSize, 2)}
}