Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FilePicker improvements. #370

Merged
merged 6 commits into from
Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/documentation/docs/controls/FilePicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import { FilePicker, IFilePickerResult } from '@pnp/spfx-controls-react/lib/File
buttonIcon="FileImage"
onSave={(filePickerResult: IFilePickerResult) => { this.setState({filePickerResult }) }}
onChanged={(filePickerResult: IFilePickerResult) => { this.setState({filePickerResult }) }}
webPartContext={this.props.context}
context={this.props.context}
/>
```

Expand All @@ -60,8 +60,8 @@ The FilePicker component can be configured with the following properties:
| buttonLabel | string | no | Specifies the label of the file picker button. |
| buttonIcon | string | no | In case it is provided the file picker will be rendered as an action button. |
| onSave | (filePickerResult: IFilePickerResult) => void | yes | Handler when the file has been selected and picker has been closed. |
| onChange | (filePickerResult: IFilePickerResult) => void | yes | Handler when the file selection has been changed. |
| webPartContext | WebPartContext | yes | Current context. |
| onChange | (filePickerResult: IFilePickerResult) => void | no | Handler when the file selection has been changed. |
| context | ApplicationCustomizerContext | WebPartContext | yes | Current context. |
| accepts | string[] | no | Array of strings containing allowed files extensions. E.g. [".gif", ".jpg", ".jpeg", ".bmp", ".dib", ".tif", ".tiff", ".ico", ".png", ".jxr", ".svg"] |
| required | boolean | no | Sets the label to inform that the value is required. |
| bingAPIKey | string | no | Used to execute WebSearch. If not provided SearchTab will not be available. |
Expand All @@ -79,12 +79,12 @@ interface `IFilePickerResult`

Provides options for carousel buttons location.

| Value | Description |
| Value | Type | Description |
| ---- | ---- |
| fileName | string | yes | File namr of the result with the extension. |
| fileNameWithoutExtension | string | yes | File namr of the result without the extension. |
| fileAbsoluteUrl | string | yes | Absolute URL of the file. Null in case of file upload. |
| file | File | yes | JS Object representing File. Will be set in case of file upload. |
| fileName | string | File namr of the result with the extension. |
| fileNameWithoutExtension | string | File name of the result without the extension. |
| fileAbsoluteUrl | string | Absolute URL of the file. Null in case of file upload. |
| downloadFileContent | () => Promise<File> | Function allows to download file content. Returns File object. |


![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/filePicker/FilePicker)
12 changes: 6 additions & 6 deletions src/controls/filePicker/FilePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export class FilePicker extends React.Component<IFilePickerProps, IFilePickerSta
telemetry.track('ReactFilePicker', {});

// Initialize file browser services
this.fileBrowserService = new FileBrowserService(props.webPartContext, this.props.itemsCountQueryLimit);
this.oneDriveService = new OneDriveService(props.webPartContext, this.props.itemsCountQueryLimit);
this.orgAssetsService = new OrgAssetsService(props.webPartContext, this.props.itemsCountQueryLimit);
this.fileSearchService = new FilesSearchService(props.webPartContext, this.props.bingAPIKey);
this.fileBrowserService = new FileBrowserService(props.context, this.props.itemsCountQueryLimit);
this.oneDriveService = new OneDriveService(props.context, this.props.itemsCountQueryLimit);
this.orgAssetsService = new OrgAssetsService(props.context, this.props.itemsCountQueryLimit);
this.fileSearchService = new FilesSearchService(props.context, this.props.bingAPIKey);

this.state = {
panelOpen: false,
Expand All @@ -68,7 +68,7 @@ export class FilePicker extends React.Component<IFilePickerProps, IFilePickerSta

const linkTabProps = {
accepts: accepts,
context: this.props.webPartContext,
context: this.props.context,
onClose: () => this._handleClosePanel(),
onSave: (value: IFilePickerResult) => { this._handleSave(value); }
};
Expand Down Expand Up @@ -201,7 +201,7 @@ export class FilePicker extends React.Component<IFilePickerProps, IFilePickerSta
* On save action
*/
private _handleSave = (filePickerResult: IFilePickerResult) => {
this.props.onChanged(filePickerResult);
this.props.onSave(filePickerResult);
this.setState({
panelOpen: false
});
Expand Down
21 changes: 19 additions & 2 deletions src/controls/filePicker/FilePicker.types.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IBreadcrumbItem } from "office-ui-fabric-react/lib/Breadcrumb";
import { IFile, ILibrary } from "../../services/FileBrowserService.types";
import { ApplicationCustomizerContext } from "@microsoft/sp-application-base";

export interface FilePickerBreadcrumbItem extends IBreadcrumbItem {
libraryData?: ILibrary;
folderData?: IFile;
}

export interface IFilePickerTab {
context: WebPartContext;
context: ApplicationCustomizerContext | WebPartContext;
accepts: string[];
onSave: (value: IFilePickerResult) => void;
onClose: () => void;
}

/**
* Represents the result of the FilePicker.
*/
export interface IFilePickerResult {
/**
* Selected file name with extension.
*/
fileName: string;
/**
* Selected file name without extension.
*/
fileNameWithoutExtension: string;
/**
* Absolute file URL. Undefined in case of file upload.
*/
fileAbsoluteUrl: string;
file: File;

/**
* Downloads file picker result content.
*/
downloadFileContent: () => Promise<File>;
}
5 changes: 3 additions & 2 deletions src/controls/filePicker/IFilePickerProps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IFilePickerResult } from "./FilePicker.types";
import { ApplicationCustomizerContext } from "@microsoft/sp-application-base";

export interface IFilePickerProps {
/**
Expand All @@ -24,12 +25,12 @@ export interface IFilePickerProps {
/**
* Handler when file has been changed.
*/
onChanged: (filePickerResult: IFilePickerResult) => void;
onChanged?: (filePickerResult: IFilePickerResult) => void;

/**
* Current context.
*/
webPartContext: WebPartContext;
context: ApplicationCustomizerContext | WebPartContext;

/**
* File extensions to be displayed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ export default class LinkFilePickerTab extends React.Component<ILinkFilePickerTa
*/
private _handleChange = (fileUrl: string) => {
const filePickerResult: IFilePickerResult = fileUrl && this._isUrl(fileUrl) ? {
file: null,
fileAbsoluteUrl: fileUrl,
fileName: GeneralHelper.getFileNameFromUrl(fileUrl),
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(fileUrl)
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(fileUrl),
downloadFileContent: () => { return this.props.fileSearchService.downloadBingContent(fileUrl, GeneralHelper.getFileNameFromUrl(fileUrl)); }
} : null;
this.setState({
filePickerResult
Expand Down Expand Up @@ -95,7 +95,7 @@ export default class LinkFilePickerTab extends React.Component<ILinkFilePickerTa
return strings.NoExternalLinksValidationMessage;
}

const fileExists = await this.props.fileSearchService.fetchFile(value);
const fileExists = await this.props.fileSearchService.checkFileExists(value);
this.setState({ isValid: fileExists });

const strResult = fileExists ? '' : strings.ProvidedValueIsInvalid;
Expand Down
3 changes: 3 additions & 0 deletions src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ export class OneDriveFilesTab extends React.Component<IOneDriveFilesTabProps, IO
* Is called when user selects a different file
*/
private _handleSelectionChange = (filePickerResult: IFilePickerResult) => {
if (filePickerResult) {
filePickerResult.downloadFileContent = () => { return this.props.oneDriveService.downloadSPFileContent(filePickerResult.fileAbsoluteUrl, filePickerResult.fileName); };
}
this.setState({
filePickerResult
});
Expand Down
5 changes: 3 additions & 2 deletions src/controls/filePicker/RecentFilesTab/RecentFilesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,12 @@ export default class RecentFilesTab extends React.Component<IRecentFilesTabProps
//Get the selected key
const selectedKey: IRecentFile = selectedItems[0] as IRecentFile;
const filePickerResult: IFilePickerResult = {
file: null,
fileAbsoluteUrl: selectedKey.fileUrl,
fileName: GeneralHelper.getFileNameFromUrl(selectedKey.fileUrl),
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(selectedKey.fileUrl)
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(selectedKey.fileUrl),
downloadFileContent: () => { return this.props.fileSearchService.downloadSPFileContent(selectedKey.fileUrl, GeneralHelper.getFileNameFromUrl(selectedKey.fileUrl)); }
};

// Save the selected file
this.setState({
filePickerResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export default class SiteFilePickerTab extends React.Component<ISiteFilePickerTa
* Is called when user selects a different file
*/
private _handleSelectionChange = (filePickerResult: IFilePickerResult) => {
if (filePickerResult) {
filePickerResult.downloadFileContent = () => { return this.props.fileBrowserService.downloadSPFileContent(filePickerResult.fileAbsoluteUrl, filePickerResult.fileName); };
}
// this.props.fileBrowserService
this.setState({
filePickerResult
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export default class UploadFilePickerTab extends React.Component<IUploadFilePick
const file: File = files[0];

const filePickerResult: IFilePickerResult = {
file,
fileAbsoluteUrl: null,
fileName: file.name,
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(file.name)
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(file.name),
downloadFileContent: () => { return Promise.resolve(file); }
};

if (GeneralHelper.isImage(file.name)) {
Expand Down
4 changes: 2 additions & 2 deletions src/controls/filePicker/WebSearchTab/WebSearchTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ export default class WebSearchTab extends React.Component<IWebSearchTabProps, IW
// even if it breaks the page.
const selectedUrl: string = selectedItem.contentUrl.replace('http://', 'https://');
selectedFileResult = {
file: null,
fileAbsoluteUrl: selectedUrl,
fileName: GeneralHelper.getFileNameFromUrl(selectedUrl),
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(selectedUrl)
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(selectedUrl),
downloadFileContent: () => { return this.props.bingSearchService.downloadBingContent(selectedUrl, GeneralHelper.getFileNameFromUrl(selectedUrl)); }
};
}

Expand Down
5 changes: 4 additions & 1 deletion src/controls/filePicker/controls/FileBrowser/FileBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
fileAbsoluteUrl: selectedItem.absoluteUrl,
fileName: GeneralHelper.getFileNameFromUrl(selectedItem.name),
fileNameWithoutExtension: GeneralHelper.getFileNameWithoutExtension(selectedItem.name),
file: null
downloadFileContent: null
};
}
this.props.onChange(filePickerResult);
Expand All @@ -443,6 +443,9 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
});
}

/**
* Handles item click.
*/
private _handleItemInvoked = (item: IFile) => {
// If a file is selected, open the library
if (item.isFolder) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './ListItemAttachments';
export * from './ChartControl';
export * from './Progress';
export * from './DateTimePicker';
export * from './FilePicker';

export * from './IFrameDialog';
export * from './IFramePanel';
Expand Down
28 changes: 26 additions & 2 deletions src/services/FileBrowserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IFile, FilesQueryResult, ILibrary } from "./FileBrowserService.types";
import { SPHttpClient } from "@microsoft/sp-http";
import { GeneralHelper } from "..";
import { ApplicationCustomizerContext } from "@microsoft/sp-application-base";

export class FileBrowserService {
protected itemsToDownloadCount: number;
protected context: WebPartContext;
protected context: ApplicationCustomizerContext | WebPartContext;

protected driveAccessToken: string;
protected mediaBaseUrl: string;
protected callerStack: string;

constructor(context: WebPartContext, itemsToDownloadCount: number = 100) {
constructor(context: ApplicationCustomizerContext | WebPartContext, itemsToDownloadCount: number = 100) {
this.context = context;

this.itemsToDownloadCount = itemsToDownloadCount;
Expand Down Expand Up @@ -45,6 +46,9 @@ export class FileBrowserService {
}


/**
* Provides the URL for file preview.
*/
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}`;
return thumbnailUrl;
Expand Down Expand Up @@ -76,6 +80,26 @@ export class FileBrowserService {
}
}

/**
* Downloads document content from SP location.
*/
public downloadSPFileContent = async (absoluteFileUrl: string, fileName: string): 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}'`);
}

// Return file created from blob
const blob : Blob = await fileDownloadResult.blob();
return new File([blob], fileName);
} catch (err) {
console.error(`[FileBrowserService.fetchFileContent] Err='${err.message}'`);
return null;
}
}

/**
* Executes query to load files with possible extension filtering
* @param restApi
Expand Down
Loading