Skip to content

Commit

Permalink
Merge pull request #861 from aaclage/Feature/DragDropFileDocUpdateV3
Browse files Browse the repository at this point in the history
DragDropFiles Control and integration with ListView and FilePicker v3
  • Loading branch information
joelfmrodrigues committed Apr 11, 2021
2 parents 63b2d42 + 302f94f commit a418d66
Show file tree
Hide file tree
Showing 20 changed files with 593 additions and 94 deletions.
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.
78 changes: 78 additions & 0 deletions docs/documentation/docs/controls/DragDropFiles.md
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)
1 change: 1 addition & 0 deletions docs/documentation/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The following controls are currently available:
- [ComboBoxListItemPicker](./controls/ComboBoxListItemPicker) (allows to select one or more items from a list)
- [Dashboard](./controls/Dashboard) (Control to render dashboard in Microsoft Teams)
- [DateTimePicker](./controls/DateTimePicker) (DateTime Picker)
- [DragDropFiles](./controls/DragDropFiles) (Allow drag and drop of files in selected areas)
- [FieldCollectionData](./controls/FieldCollectionData) (control gives you the ability to insert a list / collection data which can be used in your web part / application customizer)
- [FilePicker](./controls/FilePicker) (control that allows to browse and select a file from various places)
- [FileTypeIcon](./controls/FileTypeIcon) (Control that shows the icon of a specified file path or application)
Expand Down
1 change: 1 addition & 0 deletions docs/documentation/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nav:
- ComboBoxListItemPicker: 'controls/ComboBoxListItemPicker.md'
- Dashboard: 'controls/Dashboard.md'
- DateTimePicker: 'controls/DateTimePicker.md'
- DragDropFiles: 'controls/DragDropFiles.md'
- FieldCollectionData: 'controls/FieldCollectionData.md'
- FilePicker: 'controls/FilePicker.md'
- FileTypeIcon: 'controls/FileTypeIcon.md'
Expand Down
1 change: 1 addition & 0 deletions src/DragDropFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './controls/dragDropFiles/index';
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
.DragDropArea {
.DragDropFilesArea {
position: relative;
min-height: 150px;
}

.DragDropAreaBorder {
.DragDropFilesAreaLeave {
position: relative;
}

.DragDropFilesAreaBorder {
border: dashed grey 1px;
background-color: rgba(255,255,255,.6);
position: absolute;
Expand All @@ -14,7 +18,7 @@
z-index: 1;
}

.DragDropAreaZone {
.DragDropFilesAreaZone {
position: absolute;
top: 35%;
right: 0;
Expand Down
240 changes: 240 additions & 0 deletions src/controls/dragDropFiles/DragDropFiles.tsx
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>
);
}
}
28 changes: 28 additions & 0 deletions src/controls/dragDropFiles/IDragDropFiles.ts
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;
}
2 changes: 2 additions & 0 deletions src/controls/dragDropFiles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './IDragDropFiles';
export * from './DragDropFiles';
Loading

0 comments on commit a418d66

Please sign in to comment.