-
Notifications
You must be signed in to change notification settings - Fork 380
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #861 from aaclage/Feature/DragDropFileDocUpdateV3
DragDropFiles Control and integration with ListView and FilePicker v3
- Loading branch information
Showing
20 changed files
with
593 additions
and
94 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# DragDropFiles | ||
|
||
This control allows to drag and drop files in pre defined areas. | ||
|
||
## How to use this control in your solutions | ||
|
||
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency. | ||
- Import the following modules to your component: | ||
|
||
```TypeScript | ||
import { DragDropFiles } from "@pnp/spfx-controls-react/lib/DragDropFiles"; | ||
``` | ||
|
||
- Use the DragDropFiles control in your code as follows: | ||
|
||
```jsx | ||
<DragDropFiles | ||
dropEffect="copy" | ||
enable={true} | ||
onDrop={this._getDropFiles} | ||
iconName="Upload" | ||
labelMessage= "My custom upload File" | ||
> | ||
{/* Specify the components to load where Drag and drop area should work */} | ||
</DragDropFiles> | ||
``` | ||
**Content with drag and drop applied** | ||
|
||
```jsx | ||
<DragDropFiles | ||
dropEffect="copy" | ||
enable={true} | ||
onDrop={this._getDropFiles} | ||
iconName="Upload" | ||
labelMessage= "My custom upload File" | ||
> | ||
Drag and drop here... | ||
|
||
</DragDropFiles> | ||
``` | ||
![Custom html with drag and drop](../assets/DragDropFilesSample1.png) | ||
|
||
**ListView with drag and drop applied** | ||
|
||
![ListView control with drag and drop Control](../assets/ListView-DragDrop.png) | ||
|
||
**FilePicker with drag and drop applied** | ||
|
||
![FilePicker control with grouping](../assets/DragDropFilesSample2.png) | ||
|
||
- With the `onDrop` handler you can define a method that returns files and files inside folders that where drag and drop by user. | ||
|
||
**PS: New property "fullPath" was included in file object to allow identify dropped files based on Folders, this allow users to create associated folder path.** | ||
|
||
```typescript | ||
private _getDropFiles = (files) => { | ||
for (var i = 0; i < files.length; i++) { | ||
console.log("Filename: " + files[i].name); | ||
console.log("Path: " + files[i].fullPath); | ||
} | ||
} | ||
``` | ||
|
||
## Implementation | ||
|
||
The `DragDropFiles` can be configured with the following properties: | ||
|
||
| Property | Type | Required | Description | | ||
| ---- | ---- | ---- | ---- | | ||
| dropEffect | string | no | Visual feedback given to user during a drag and drop operation (copy,move,link,none). Default value is `copy`. | | ||
| enable | boolean | no | Option allow control to be enable or disable. Default value is `true`| | ||
| labelMessage | string | no | Message displayed in drag drop preview. | | ||
| onDrop | any | no | Method that returns all Files[] from drag and drop file area. | | ||
| iconName | string | no | Icon Name from Office UI Fabric Icons. | | ||
|
||
|
||
|
||
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/DragDropFiles) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './controls/dragDropFiles/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
import * as React from 'react'; | ||
import styles from './DragDropFiles.module.scss'; | ||
import { Icon } from 'office-ui-fabric-react/lib/Icon'; | ||
import { IDragDropFilesState, IDragDropFilesProps } from './IDragDropFiles'; | ||
import * as strings from 'ControlStrings'; | ||
|
||
/** | ||
* DragDropFiles Class Control | ||
*/ | ||
export class DragDropFiles extends React.Component<IDragDropFilesProps, IDragDropFilesState> { | ||
private dragCounter = 0; | ||
private dropRef = React.createRef<HTMLDivElement>(); | ||
private _LabelMessage; | ||
private _IconName; | ||
private _dropEffect; | ||
private _enable; | ||
|
||
constructor(props: IDragDropFilesProps) { | ||
super(props); | ||
// Initialize state | ||
this.state = { | ||
dragStatus: false | ||
}; | ||
|
||
} | ||
|
||
/** | ||
* Lifecycle hook when component is mounted | ||
*/ | ||
public componentDidMount(): void { | ||
const { dropEffect, enable } = this.props; | ||
if (dropEffect == undefined || dropEffect == "") { | ||
this._dropEffect = "copy"; | ||
} else { | ||
this._dropEffect = dropEffect; | ||
} | ||
if (enable == undefined) { | ||
this._enable = true; | ||
} else { | ||
this._enable = enable; | ||
} | ||
// Add EventListeners for drag zone area | ||
let divDropArea = this.dropRef.current; | ||
if (this._enable == true) { | ||
divDropArea.addEventListener('dragenter', this.handleonDragEnter); | ||
divDropArea.addEventListener('dragleave', this.handleonDragLeave); | ||
divDropArea.addEventListener('dragover', this.handleonDragOver); | ||
divDropArea.addEventListener('drop', this.handleonDrop); | ||
} | ||
} | ||
|
||
/** | ||
* Stop listeners from onDragOver event. | ||
* @param e | ||
*/ | ||
private handleonDragOver = (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { | ||
e.dataTransfer.dropEffect = e.dataTransfer.dropEffect = this.props.dropEffect; | ||
} | ||
} | ||
/** | ||
* Stop listeners from onDragEnter event, enable drag and drop view. | ||
* @param e | ||
*/ | ||
private handleonDragEnter = (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.dragCounter++; | ||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { | ||
e.dataTransfer.dropEffect = this._dropEffect; | ||
this.setState({ dragStatus: true }); | ||
} | ||
} | ||
/** | ||
* Stop listeners from ondragenter event, disable drag and drop view. | ||
* @param e | ||
*/ | ||
private handleonDragLeave = (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.dragCounter--; | ||
if (this.dragCounter === 0) { | ||
this.setState({ dragStatus: false }); | ||
} | ||
} | ||
/** | ||
* Stop listeners from onDrop event and load files to property onDrop. | ||
* @param e | ||
*/ | ||
private handleonDrop = async (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.setState({ dragStatus: false }); | ||
if (e.dataTransfer && e.dataTransfer.items) { | ||
this.props.onDrop(await this.getFilesAsync(e)); | ||
} | ||
e.dataTransfer.clearData(); | ||
this.dragCounter = 0; | ||
} | ||
|
||
/** | ||
* Add File to Array Files of type File[] | ||
* https://www.meziantou.net/upload-files-and-directories-using-an-input-drag-and-drop-or-copy-and-paste-with.htm | ||
* @param dataTransfer | ||
*/ | ||
private getFilesAsync = async (e) => { | ||
const Customfiles = e.dataTransfer; | ||
const items = Customfiles.items; | ||
const Directory = []; | ||
let entry: any; | ||
const files: File[] = []; | ||
for (let i = 0; i < items.length; i++) { | ||
const item = items[i]; | ||
if (item.kind === "file") { | ||
/** | ||
* This method retrieves Files from Folders | ||
* defensive code to only use method when exist in browser if not only return files. | ||
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry | ||
*/ | ||
if (item.getAsEntry) { | ||
entry = item.getAsEntry(); | ||
if (entry.isDirectory) { | ||
Directory.push(entry); | ||
} else { | ||
const file = item.getAsFile(); | ||
if (file) { | ||
file.fullPath = ""; | ||
files.push(file); | ||
} | ||
} | ||
} else if (item.webkitGetAsEntry) { | ||
entry = item.webkitGetAsEntry(); | ||
if (entry.isDirectory) { | ||
Directory.push(entry); | ||
} else { | ||
const file = item.getAsFile(); | ||
if (file) { | ||
file.fullPath = ""; | ||
files.push(file); | ||
} | ||
} | ||
} else if ("function" == typeof item.getAsFile) { | ||
const file = item.getAsFile(); | ||
if (file) { | ||
file.fullPath = ""; | ||
files.push(file); | ||
} | ||
} | ||
continue; | ||
} | ||
} | ||
if (Directory.length > 0) { | ||
const entryContent = await this.readEntryContentAsync(Directory); | ||
files.push(...entryContent); | ||
} | ||
return files; | ||
} | ||
|
||
// Returns a promise with all the files of the directory hierarchy | ||
/** | ||
* | ||
* @param entry | ||
*/ | ||
private readEntryContentAsync = (Directory) => { | ||
return new Promise<File[]>((resolve, reject) => { | ||
let reading = 0; | ||
const contents: File[] = []; | ||
Directory.forEach(entry => { | ||
readEntry(entry, ""); | ||
}); | ||
|
||
function readEntry(entry, path) { | ||
if (entry.isDirectory) { | ||
readReaderContent(entry.createReader()); | ||
} else { | ||
reading++; | ||
entry.file(file => { | ||
reading--; | ||
file.fullPath = path; | ||
contents.push(file); | ||
|
||
if (reading === 0) { | ||
resolve(contents); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
function readReaderContent(reader) { | ||
reading++; | ||
|
||
reader.readEntries((entries) => { | ||
reading--; | ||
for (const entry of entries) { | ||
readEntry(entry, entry.fullPath); | ||
} | ||
|
||
if (reading === 0) { | ||
resolve(contents); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Default React component render method | ||
*/ | ||
public render(): React.ReactElement<IDragDropFilesProps> { | ||
let { dragStatus } = this.state; | ||
let { labelMessage, iconName } = this.props; | ||
|
||
if (labelMessage == undefined || labelMessage == "") { | ||
this._LabelMessage = ""; | ||
} else { | ||
this._LabelMessage = labelMessage; | ||
} | ||
if (iconName == undefined || iconName == "") { | ||
this._IconName = ""; | ||
} else { | ||
this._IconName = iconName; | ||
} | ||
return ( | ||
<div className={(dragStatus && this._enable) ? styles.DragDropFilesArea : styles.DragDropFilesAreaLeave} | ||
ref={this.dropRef}> | ||
{(dragStatus && this._enable) && | ||
<div className={styles.DragDropFilesAreaBorder}> | ||
<div className={styles.DragDropFilesAreaZone}> | ||
<Icon iconName={this._IconName} className="ms-IconExample" /> | ||
<div>{this._LabelMessage}</div> | ||
</div> | ||
</div> | ||
} | ||
{this.props.children} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export interface IDragDropFilesProps { | ||
/** | ||
* Specifies the dropEffect, default is 'none' | ||
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect | ||
*/ | ||
dropEffect?: string; | ||
/** | ||
* Specifies the label of the file picker button | ||
*/ | ||
labelMessage?: string; | ||
|
||
/** | ||
* Specifies the icon to display | ||
*/ | ||
iconName?: string; | ||
/** | ||
* Handler to return the files from drag and drop. | ||
**/ | ||
onDrop?: any; | ||
/** | ||
* Specify if drag and drop option is enable. | ||
**/ | ||
enable?: boolean; | ||
} | ||
|
||
export interface IDragDropFilesState { | ||
dragStatus?: boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './IDragDropFiles'; | ||
export * from './DragDropFiles'; |
Oops, something went wrong.