Skip to content

Commit 9924e0d

Browse files
committed
DragDropFiles Control and integration with ListView and FilePicker
1 parent 659d324 commit 9924e0d

19 files changed

+574
-86
lines changed
11.1 KB
Loading
18.2 KB
Loading
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# DragDropFiles
2+
3+
This control allows to drag and drop files in pre defined areas.
4+
5+
## How to use this control in your solutions
6+
7+
- 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.
8+
- Import the following modules to your component:
9+
10+
```TypeScript
11+
import { DragDropFiles } from "@pnp/spfx-controls-react/lib/DragDropFiles";
12+
```
13+
14+
- Use the DragDropFiles control in your code as follows:
15+
16+
```jsx
17+
<DragDropFiles
18+
dropEffect="copy"
19+
enable={true}
20+
onDrop={this._getDropFiles}
21+
iconName="Upload"
22+
labelMessage= "My custom upload File"
23+
>
24+
{/* Specify the components to load where Drag and drop area should work */}
25+
</DragDropFiles>
26+
```
27+
**Content with drag and drop applied**
28+
29+
```jsx
30+
<DragDropFiles
31+
dropEffect="copy"
32+
enable={true}
33+
onDrop={this._getDropFiles}
34+
iconName="Upload"
35+
labelMessage= "My custom upload File"
36+
>
37+
Drag and drop here...
38+
39+
</DragDropFiles>
40+
```
41+
![Custom html with drag and drop](../assets/DragDropFilesSample1.png)
42+
43+
**ListView with drag and drop applied**
44+
45+
![ListView control with drag and drop Control](../assets/ListView-DragDrop.png)
46+
47+
**FilePicker with drag and drop applied**
48+
49+
![FilePicker control with grouping](../assets/DragDropFilesSample2.png)
50+
51+
- With the `onDrop` handler you can define a method that returns files and files inside folders that where drag and drop by user.
52+
53+
**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.**
54+
55+
```typescript
56+
private _getDropFiles = (files) => {
57+
for (var i = 0; i < files.length; i++) {
58+
console.log("Filename: " + files[i].name);
59+
console.log("Path: " + files[i].fullPath);
60+
}
61+
}
62+
```
63+
64+
## Implementation
65+
66+
The `DragDropFiles` can be configured with the following properties:
67+
68+
| Property | Type | Required | Description |
69+
| ---- | ---- | ---- | ---- |
70+
| dropEffect | string | no | Visual feedback given to user during a drag and drop operation (copy,move,link,none). Default value is `copy`. |
71+
| enable | boolean | no | Option allow control to be enable or disable. Default value is `true`|
72+
| labelMessage | string | no | Message displayed in drag drop preview. |
73+
| onDrop | any | no | Method that returns all Files[] from drag and drop file area. |
74+
| iconName | string | no | Icon Name from Office UI Fabric Icons. |
75+
76+
77+
78+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/DragDropFiles)

docs/documentation/docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ The following controls are currently available:
5959
- [Charts](./controls/ChartControl) (makes it easy to integrate [Chart.js](https://www.chartjs.org/) charts into web part)
6060
- [ComboBoxListItemPicker](./controls/ComboBoxListItemPicker) (allows to select one or more items from a list)
6161
- [Dashboard](./controls/Dashboard) (Control to render dashboard in Microsoft Teams)
62+
- [DragDropFiles](./controls/DragDropFiles) (Allow drag and drop of files in selected areas)
6263
- [DateTimePicker](./controls/DateTimePicker) (DateTime Picker)
6364
- [FieldCollectionData](./controls/FieldCollectionData) (control gives you the ability to insert a list / collection data which can be used in your web part / application customizer)
6465
- [FilePicker](./controls/FilePicker) (control that allows to browse and select a file from various places)

src/DragDropFiles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/dragDropFiles/index';

src/controls/listView/ListView.DragDrop.module.scss renamed to src/controls/dragDropFiles/DragDropFiles.module.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
.DragDropArea {
1+
.DragDropFilesArea {
22
position: relative;
33
min-height: 150px;
44
}
55

6-
.DragDropAreaBorder {
6+
.DragDropFilesAreaLeave {
7+
position: relative;
8+
}
9+
10+
.DragDropFilesAreaBorder {
711
border: dashed grey 1px;
812
background-color: rgba(255,255,255,.6);
913
position: absolute;
@@ -14,7 +18,7 @@
1418
z-index: 1;
1519
}
1620

17-
.DragDropAreaZone {
21+
.DragDropFilesAreaZone {
1822
position: absolute;
1923
top: 35%;
2024
right: 0;
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import * as React from 'react';
2+
import styles from './DragDropFiles.module.scss';
3+
import { Icon } from 'office-ui-fabric-react/lib/Icon';
4+
import { IDragDropFilesState, IDragDropFilesProps } from './IDragDropFiles';
5+
import * as strings from 'ControlStrings';
6+
7+
/**
8+
* DragDropFiles Class Control
9+
*/
10+
export class DragDropFiles extends React.Component<IDragDropFilesProps, IDragDropFilesState> {
11+
private dragCounter = 0;
12+
private dropRef = React.createRef<HTMLDivElement>();
13+
private _LabelMessage;
14+
private _IconName;
15+
private _dropEffect;
16+
private _enable;
17+
18+
constructor(props: IDragDropFilesProps) {
19+
super(props);
20+
// Initialize state
21+
this.state = {
22+
dragStatus: false
23+
};
24+
25+
}
26+
27+
/**
28+
* Lifecycle hook when component is mounted
29+
*/
30+
public componentDidMount(): void {
31+
const { dropEffect, enable } = this.props;
32+
if (dropEffect == undefined || dropEffect == "") {
33+
this._dropEffect = "copy";
34+
} else {
35+
this._dropEffect = dropEffect;
36+
}
37+
if (enable == undefined) {
38+
this._enable = true;
39+
} else {
40+
this._enable = enable;
41+
}
42+
// Add EventListeners for drag zone area
43+
let divDropArea = this.dropRef.current;
44+
if(this._enable == true)
45+
{
46+
divDropArea.addEventListener('dragenter', this.handleonDragEnter);
47+
divDropArea.addEventListener('dragleave', this.handleonDragLeave);
48+
divDropArea.addEventListener('dragover', this.handleonDragOver);
49+
divDropArea.addEventListener('drop', this.handleonDrop);
50+
}
51+
}
52+
53+
/**
54+
* Stop listeners from onDragOver event.
55+
* @param e
56+
*/
57+
private handleonDragOver = (e) => {
58+
e.preventDefault();
59+
e.stopPropagation();
60+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
61+
e.dataTransfer.dropEffect = e.dataTransfer.dropEffect = this.props.dropEffect;
62+
}
63+
}
64+
/**
65+
* Stop listeners from onDragEnter event, enable drag and drop view.
66+
* @param e
67+
*/
68+
private handleonDragEnter = (e) => {
69+
e.preventDefault();
70+
e.stopPropagation();
71+
this.dragCounter++;
72+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
73+
e.dataTransfer.dropEffect = this._dropEffect;
74+
this.setState({ dragStatus: true });
75+
}
76+
}
77+
/**
78+
* Stop listeners from ondragenter event, disable drag and drop view.
79+
* @param e
80+
*/
81+
private handleonDragLeave = (e) => {
82+
e.preventDefault();
83+
e.stopPropagation();
84+
this.dragCounter--;
85+
if (this.dragCounter === 0) {
86+
this.setState({ dragStatus: false });
87+
}
88+
}
89+
/**
90+
* Stop listeners from onDrop event and load files to property onDrop.
91+
* @param e
92+
*/
93+
private handleonDrop = async (e) => {
94+
e.preventDefault();
95+
e.stopPropagation();
96+
this.setState({ dragStatus: false });
97+
if (e.dataTransfer && e.dataTransfer.items) {
98+
this.props.onDrop(await this.getFilesAsync(e));
99+
}
100+
e.dataTransfer.clearData();
101+
this.dragCounter = 0;
102+
}
103+
104+
/**
105+
* Add File to Array Files of type File[]
106+
* https://www.meziantou.net/upload-files-and-directories-using-an-input-drag-and-drop-or-copy-and-paste-with.htm
107+
* @param dataTransfer
108+
*/
109+
private getFilesAsync = async (e) => {
110+
const Customfiles = e.dataTransfer;
111+
const items = Customfiles.items;
112+
const Directory = [];
113+
const files: File[] = [];
114+
for (let i = 0; i < items.length; i++) {
115+
const item = items[i];
116+
if (item.kind === "file") {
117+
if (typeof item.webkitGetAsEntry === "function") {
118+
const entry = item.webkitGetAsEntry();
119+
if (entry.isDirectory) {
120+
Directory.push(entry);
121+
}else{
122+
const file = item.getAsFile();
123+
if (file) {
124+
file.fullPath = "";
125+
files.push(file);
126+
}
127+
}
128+
continue;
129+
}
130+
}
131+
}
132+
if(Directory.length > 0)
133+
{
134+
const entryContent = await this.readEntryContentAsync(Directory);
135+
files.push(...entryContent);
136+
}
137+
return files;
138+
}
139+
140+
// Returns a promise with all the files of the directory hierarchy
141+
/**
142+
*
143+
* @param entry
144+
*/
145+
private readEntryContentAsync = (Directory) => {
146+
return new Promise<File[]>((resolve, reject) => {
147+
let reading = 0;
148+
const contents: File[] = [];
149+
Directory.forEach(entry => {
150+
readEntry(entry, "");
151+
});
152+
153+
function readEntry(entry, path) {
154+
if (entry.isDirectory) {
155+
readReaderContent(entry.createReader());
156+
} else {
157+
reading++;
158+
entry.file(file => {
159+
reading--;
160+
file.fullPath = path;
161+
contents.push(file);
162+
163+
if (reading === 0) {
164+
resolve(contents);
165+
}
166+
});
167+
}
168+
}
169+
170+
function readReaderContent(reader) {
171+
reading++;
172+
173+
reader.readEntries((entries)=> {
174+
reading--;
175+
for (const entry of entries) {
176+
readEntry(entry, entry.fullPath);
177+
}
178+
179+
if (reading === 0) {
180+
resolve(contents);
181+
}
182+
});
183+
}
184+
});
185+
}
186+
187+
/**
188+
* Default React component render method
189+
*/
190+
public render(): React.ReactElement<IDragDropFilesProps> {
191+
let { dragStatus } = this.state;
192+
let { labelMessage, iconName } = this.props;
193+
194+
if (labelMessage == undefined || labelMessage == "") {
195+
this._LabelMessage = "";
196+
} else {
197+
this._LabelMessage = labelMessage;
198+
}
199+
if (iconName == undefined || iconName == "") {
200+
this._IconName = "";
201+
} else {
202+
this._IconName = iconName;
203+
}
204+
return (
205+
<div className={(dragStatus && this._enable) ? styles.DragDropFilesArea : styles.DragDropFilesAreaLeave}
206+
ref={this.dropRef}>
207+
{(dragStatus && this._enable) &&
208+
<div className={styles.DragDropFilesAreaBorder}>
209+
<div className={styles.DragDropFilesAreaZone}>
210+
<Icon iconName={this._IconName} className="ms-IconExample" />
211+
<div>{this._LabelMessage}</div>
212+
</div>
213+
</div>
214+
}
215+
{this.props.children}
216+
</div>
217+
);
218+
}
219+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export interface IDragDropFilesProps {
2+
/**
3+
* Specifies the dropEffect, default is 'none'
4+
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect
5+
*/
6+
dropEffect?: string;
7+
/**
8+
* Specifies the label of the file picker button
9+
*/
10+
labelMessage?: string;
11+
12+
/**
13+
* Specifies the icon to display
14+
*/
15+
iconName?: string;
16+
/**
17+
* Handler to return the files from drag and drop.
18+
**/
19+
onDrop?: any;
20+
/**
21+
* Specify if drag and drop option is enable.
22+
**/
23+
enable?: boolean;
24+
}
25+
26+
export interface IDragDropFilesState {
27+
dragStatus?: boolean;
28+
}

src/controls/dragDropFiles/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './IDragDropFiles';
2+
export * from './DragDropFiles';

0 commit comments

Comments
 (0)