Skip to content

Commit a418d66

Browse files
Merge pull request #861 from aaclage/Feature/DragDropFileDocUpdateV3
DragDropFiles Control and integration with ListView and FilePicker v3
2 parents 63b2d42 + 302f94f commit a418d66

20 files changed

+593
-94
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
@@ -66,6 +66,7 @@ The following controls are currently available:
6666
- [ComboBoxListItemPicker](./controls/ComboBoxListItemPicker) (allows to select one or more items from a list)
6767
- [Dashboard](./controls/Dashboard) (Control to render dashboard in Microsoft Teams)
6868
- [DateTimePicker](./controls/DateTimePicker) (DateTime Picker)
69+
- [DragDropFiles](./controls/DragDropFiles) (Allow drag and drop of files in selected areas)
6970
- [FieldCollectionData](./controls/FieldCollectionData) (control gives you the ability to insert a list / collection data which can be used in your web part / application customizer)
7071
- [FilePicker](./controls/FilePicker) (control that allows to browse and select a file from various places)
7172
- [FileTypeIcon](./controls/FileTypeIcon) (Control that shows the icon of a specified file path or application)

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ nav:
2626
- ComboBoxListItemPicker: 'controls/ComboBoxListItemPicker.md'
2727
- Dashboard: 'controls/Dashboard.md'
2828
- DateTimePicker: 'controls/DateTimePicker.md'
29+
- DragDropFiles: 'controls/DragDropFiles.md'
2930
- FieldCollectionData: 'controls/FieldCollectionData.md'
3031
- FilePicker: 'controls/FilePicker.md'
3132
- FileTypeIcon: 'controls/FileTypeIcon.md'

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: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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+
divDropArea.addEventListener('dragenter', this.handleonDragEnter);
46+
divDropArea.addEventListener('dragleave', this.handleonDragLeave);
47+
divDropArea.addEventListener('dragover', this.handleonDragOver);
48+
divDropArea.addEventListener('drop', this.handleonDrop);
49+
}
50+
}
51+
52+
/**
53+
* Stop listeners from onDragOver event.
54+
* @param e
55+
*/
56+
private handleonDragOver = (e) => {
57+
e.preventDefault();
58+
e.stopPropagation();
59+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
60+
e.dataTransfer.dropEffect = e.dataTransfer.dropEffect = this.props.dropEffect;
61+
}
62+
}
63+
/**
64+
* Stop listeners from onDragEnter event, enable drag and drop view.
65+
* @param e
66+
*/
67+
private handleonDragEnter = (e) => {
68+
e.preventDefault();
69+
e.stopPropagation();
70+
this.dragCounter++;
71+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
72+
e.dataTransfer.dropEffect = this._dropEffect;
73+
this.setState({ dragStatus: true });
74+
}
75+
}
76+
/**
77+
* Stop listeners from ondragenter event, disable drag and drop view.
78+
* @param e
79+
*/
80+
private handleonDragLeave = (e) => {
81+
e.preventDefault();
82+
e.stopPropagation();
83+
this.dragCounter--;
84+
if (this.dragCounter === 0) {
85+
this.setState({ dragStatus: false });
86+
}
87+
}
88+
/**
89+
* Stop listeners from onDrop event and load files to property onDrop.
90+
* @param e
91+
*/
92+
private handleonDrop = async (e) => {
93+
e.preventDefault();
94+
e.stopPropagation();
95+
this.setState({ dragStatus: false });
96+
if (e.dataTransfer && e.dataTransfer.items) {
97+
this.props.onDrop(await this.getFilesAsync(e));
98+
}
99+
e.dataTransfer.clearData();
100+
this.dragCounter = 0;
101+
}
102+
103+
/**
104+
* Add File to Array Files of type File[]
105+
* https://www.meziantou.net/upload-files-and-directories-using-an-input-drag-and-drop-or-copy-and-paste-with.htm
106+
* @param dataTransfer
107+
*/
108+
private getFilesAsync = async (e) => {
109+
const Customfiles = e.dataTransfer;
110+
const items = Customfiles.items;
111+
const Directory = [];
112+
let entry: any;
113+
const files: File[] = [];
114+
for (let i = 0; i < items.length; i++) {
115+
const item = items[i];
116+
if (item.kind === "file") {
117+
/**
118+
* This method retrieves Files from Folders
119+
* defensive code to only use method when exist in browser if not only return files.
120+
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
121+
*/
122+
if (item.getAsEntry) {
123+
entry = item.getAsEntry();
124+
if (entry.isDirectory) {
125+
Directory.push(entry);
126+
} else {
127+
const file = item.getAsFile();
128+
if (file) {
129+
file.fullPath = "";
130+
files.push(file);
131+
}
132+
}
133+
} else if (item.webkitGetAsEntry) {
134+
entry = item.webkitGetAsEntry();
135+
if (entry.isDirectory) {
136+
Directory.push(entry);
137+
} else {
138+
const file = item.getAsFile();
139+
if (file) {
140+
file.fullPath = "";
141+
files.push(file);
142+
}
143+
}
144+
} else if ("function" == typeof item.getAsFile) {
145+
const file = item.getAsFile();
146+
if (file) {
147+
file.fullPath = "";
148+
files.push(file);
149+
}
150+
}
151+
continue;
152+
}
153+
}
154+
if (Directory.length > 0) {
155+
const entryContent = await this.readEntryContentAsync(Directory);
156+
files.push(...entryContent);
157+
}
158+
return files;
159+
}
160+
161+
// Returns a promise with all the files of the directory hierarchy
162+
/**
163+
*
164+
* @param entry
165+
*/
166+
private readEntryContentAsync = (Directory) => {
167+
return new Promise<File[]>((resolve, reject) => {
168+
let reading = 0;
169+
const contents: File[] = [];
170+
Directory.forEach(entry => {
171+
readEntry(entry, "");
172+
});
173+
174+
function readEntry(entry, path) {
175+
if (entry.isDirectory) {
176+
readReaderContent(entry.createReader());
177+
} else {
178+
reading++;
179+
entry.file(file => {
180+
reading--;
181+
file.fullPath = path;
182+
contents.push(file);
183+
184+
if (reading === 0) {
185+
resolve(contents);
186+
}
187+
});
188+
}
189+
}
190+
191+
function readReaderContent(reader) {
192+
reading++;
193+
194+
reader.readEntries((entries) => {
195+
reading--;
196+
for (const entry of entries) {
197+
readEntry(entry, entry.fullPath);
198+
}
199+
200+
if (reading === 0) {
201+
resolve(contents);
202+
}
203+
});
204+
}
205+
});
206+
}
207+
208+
/**
209+
* Default React component render method
210+
*/
211+
public render(): React.ReactElement<IDragDropFilesProps> {
212+
let { dragStatus } = this.state;
213+
let { labelMessage, iconName } = this.props;
214+
215+
if (labelMessage == undefined || labelMessage == "") {
216+
this._LabelMessage = "";
217+
} else {
218+
this._LabelMessage = labelMessage;
219+
}
220+
if (iconName == undefined || iconName == "") {
221+
this._IconName = "";
222+
} else {
223+
this._IconName = iconName;
224+
}
225+
return (
226+
<div className={(dragStatus && this._enable) ? styles.DragDropFilesArea : styles.DragDropFilesAreaLeave}
227+
ref={this.dropRef}>
228+
{(dragStatus && this._enable) &&
229+
<div className={styles.DragDropFilesAreaBorder}>
230+
<div className={styles.DragDropFilesAreaZone}>
231+
<Icon iconName={this._IconName} className="ms-IconExample" />
232+
<div>{this._LabelMessage}</div>
233+
</div>
234+
</div>
235+
}
236+
{this.props.children}
237+
</div>
238+
);
239+
}
240+
}
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)