mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
improved uploading of files (#1730)
* improved uploading of files - new core/UploadDirective to allow dropping files to any html element - enhanced file dropping for DataTable rows (disabled by default) - enhanced file dropping for DocumentList rows (disabled by default) - upload drop area now handles file uploads for child elements (i.e. rows in the document list) * fix unit tests * unit tests and code cleanup * #1732, fix upload of folders
This commit is contained in:
committed by
Mario Romano
parent
f3de023ab3
commit
3fee3b5002
@@ -18,6 +18,7 @@
|
|||||||
[currentFolderId]="currentFolderId"
|
[currentFolderId]="currentFolderId"
|
||||||
[contextMenuActions]="true"
|
[contextMenuActions]="true"
|
||||||
[contentActions]="true"
|
[contentActions]="true"
|
||||||
|
[allowDropFiles]="true"
|
||||||
(error)="onNavigationError($event)"
|
(error)="onNavigationError($event)"
|
||||||
(success)="resetError()"
|
(success)="resetError()"
|
||||||
(preview)="showFile($event)">
|
(preview)="showFile($event)">
|
||||||
|
@@ -43,26 +43,134 @@ necessary configuration, see this [page](https://github.com/Alfresco/alfresco-ng
|
|||||||
npm install --save ng2-alfresco-core
|
npm install --save ng2-alfresco-core
|
||||||
```
|
```
|
||||||
|
|
||||||
## Main components and services
|
## Library content
|
||||||
|
|
||||||
### Components
|
- Components
|
||||||
|
- Context Menu directive
|
||||||
|
- Material Design directives
|
||||||
|
- [mdl]
|
||||||
|
- [alfresco-mdl-button]
|
||||||
|
- [alfresco-mdl-menu]
|
||||||
|
- [alfresco-mdl-tabs]
|
||||||
|
- Directives
|
||||||
|
- UploadDirective
|
||||||
|
- Services
|
||||||
|
- **LogService**, log service implementation
|
||||||
|
- **NotificationService**, Notification service implementation
|
||||||
|
- **AlfrescoApiService**, provides access to Alfresco JS API instance
|
||||||
|
- **AlfrescoAuthenticationService**, main authentication APIs
|
||||||
|
- **AlfrescoTranslationService**, various i18n-related APIs
|
||||||
|
- **ContextMenuService**, global context menu APIs
|
||||||
|
|
||||||
- Context Menu directive
|
|
||||||
- Material Design directives
|
|
||||||
- [mdl]
|
|
||||||
- [alfresco-mdl-button]
|
|
||||||
- [alfresco-mdl-menu]
|
|
||||||
- [alfresco-mdl-tabs]
|
|
||||||
|
|
||||||
### Services
|
## UploadDirective
|
||||||
|
|
||||||
- **LogService**, log service implementation
|
Allows your components or common HTML elements reacting on File drag and drop in order to upload content.
|
||||||
- **NotificationService**, Notification service implementation
|
Used by attaching to an element or component.
|
||||||
- **AlfrescoApiService**, provides access to Alfresco JS API instance
|
|
||||||
- **AlfrescoAuthenticationService**, main authentication APIs
|
|
||||||
- **AlfrescoTranslationService**, various i18n-related APIs
|
|
||||||
- **ContextMenuService**, global context menu APIs
|
|
||||||
|
|
||||||
|
### Basic usage
|
||||||
|
|
||||||
|
The directive itself does not do any file management process,
|
||||||
|
but collects information on dropped files and raises corresponding events instead.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div style="width:100px; height:100px"
|
||||||
|
[adf-upload]="true"
|
||||||
|
[adf-upload-data]="{ some: 'data' }">
|
||||||
|
Drop files here...
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible controlling when upload behaviour is enabled/disabled by binding directive to a `boolean` value or expression:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div [adf-upload]="true">...</div>
|
||||||
|
<div [adf-upload]="allowUpload">...</div>
|
||||||
|
<div [adf-upload]="isUploadEnabled()">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
Once a single or multiple files are dropped on the decorated element the `upload-files` [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) is raised.
|
||||||
|
The DOM event is configured to have `bubbling` enabled, so any component up the component tree can handle, process or prevent it:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div (upload-files)="onUploadFiles($event)">
|
||||||
|
<div [adf-upload]="true"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
onUploadFiles(e: CustomEvent) {
|
||||||
|
console.log(e.detail.files);
|
||||||
|
|
||||||
|
// your code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that event will be raised only if valid [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) were dropped onto the decorated element.
|
||||||
|
|
||||||
|
The `upload-files` event is cancellable, so you can stop propagation of the drop event to uppper levels in case it has been already handled by your code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
onUploadFiles(e: CustomEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// your code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible attaching arbitrary data to each event in order to access it from within external event handlers.
|
||||||
|
A typical scenario is data tables where you may want to handle also the data row and/or underlying data to be accessible upon files drop.
|
||||||
|
|
||||||
|
You may be using `adf-upload-data` to bind custom values or objects for every event raised:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div [adf-upload]="true" [adf-upload-data]="dataRow"></div>
|
||||||
|
<div [adf-upload]="true" [adf-upload-data]="'string value'"></div>
|
||||||
|
<div [adf-upload]="true" [adf-upload-data]="{ name: 'custom object' }"></div>
|
||||||
|
<div [adf-upload]="true" [adf-upload-data]="getUploadData()"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
As part of the `details` property of the [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) you can get access to the following:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
detail: {
|
||||||
|
sender: UploadDirective, // directive that raised given event
|
||||||
|
data: any, // arbitrary data associated (bound)
|
||||||
|
files: File[] // dropped files
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
The decorated element gets `adf-upload__dragging` CSS class name in the class list every time files are dragged over it.
|
||||||
|
This allows changing look and feel of your components in case additional visual indication is required,
|
||||||
|
for example you may want drawing a dashed border around the table row on drag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table>
|
||||||
|
<tr [adf-upload]="true">
|
||||||
|
...
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.adf-upload__dragging > td:first-child {
|
||||||
|
border-left: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-upload__dragging > td {
|
||||||
|
border-top: 1px dashed rgb(68,138,255);
|
||||||
|
border-bottom: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-upload__dragging > td:last-child {
|
||||||
|
border-right: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Alfresco Api Service
|
## Alfresco Api Service
|
||||||
|
|
||||||
@@ -78,7 +186,7 @@ export class MyComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
let nodeId = 'some-node-id';
|
let nodeId = 'some-node-id';
|
||||||
let params = {};
|
let params = {};
|
||||||
this.getAlfrescoApi().nodes
|
this.apiService.getInstance().nodes
|
||||||
.getNodeChildren(nodeId, params)
|
.getNodeChildren(nodeId, params)
|
||||||
.then(result => console.log(result));
|
.then(result => console.log(result));
|
||||||
}
|
}
|
||||||
@@ -92,8 +200,8 @@ In case of any TypeScript type check errors you can still call any supported
|
|||||||
Alfresco JS api by casting the instance to `any` type like the following:_
|
Alfresco JS api by casting the instance to `any` type like the following:_
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
let apiService: any = this.authService.getAlfrescoApi();
|
let api: any = this.apiService.getInstance();
|
||||||
apiService.nodes.addNode('-root-', body, {});
|
api.nodes.addNode('-root-', body, {});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notification Service
|
## Notification Service
|
||||||
|
@@ -39,6 +39,7 @@ import {
|
|||||||
NotificationService
|
NotificationService
|
||||||
} from './src/services/index';
|
} from './src/services/index';
|
||||||
|
|
||||||
|
import { UploadDirective } from './src/directives/upload.directive';
|
||||||
import { DataColumnComponent } from './src/components/data-column/data-column.component';
|
import { DataColumnComponent } from './src/components/data-column/data-column.component';
|
||||||
import { DataColumnListComponent } from './src/components/data-column/data-column-list.component';
|
import { DataColumnListComponent } from './src/components/data-column/data-column-list.component';
|
||||||
import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index';
|
import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index';
|
||||||
@@ -48,6 +49,7 @@ export * from './src/services/index';
|
|||||||
export * from './src/components/index';
|
export * from './src/components/index';
|
||||||
export * from './src/components/data-column/data-column.component';
|
export * from './src/components/data-column/data-column.component';
|
||||||
export * from './src/components/data-column/data-column-list.component';
|
export * from './src/components/data-column/data-column-list.component';
|
||||||
|
export * from './src/directives/upload.directive';
|
||||||
export * from './src/utils/index';
|
export * from './src/utils/index';
|
||||||
export * from './src/events/base.event';
|
export * from './src/events/base.event';
|
||||||
export * from './src/events/base-ui.event';
|
export * from './src/events/base-ui.event';
|
||||||
@@ -90,6 +92,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
|
|||||||
declarations: [
|
declarations: [
|
||||||
...MATERIAL_DESIGN_DIRECTIVES,
|
...MATERIAL_DESIGN_DIRECTIVES,
|
||||||
...CONTEXT_MENU_DIRECTIVES,
|
...CONTEXT_MENU_DIRECTIVES,
|
||||||
|
UploadDirective,
|
||||||
DataColumnComponent,
|
DataColumnComponent,
|
||||||
DataColumnListComponent
|
DataColumnListComponent
|
||||||
],
|
],
|
||||||
@@ -105,6 +108,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
...MATERIAL_DESIGN_DIRECTIVES,
|
...MATERIAL_DESIGN_DIRECTIVES,
|
||||||
...CONTEXT_MENU_DIRECTIVES,
|
...CONTEXT_MENU_DIRECTIVES,
|
||||||
|
UploadDirective,
|
||||||
DataColumnComponent,
|
DataColumnComponent,
|
||||||
DataColumnListComponent
|
DataColumnListComponent
|
||||||
]
|
]
|
||||||
|
@@ -0,0 +1,136 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ElementRef } from '@angular/core';
|
||||||
|
import { UploadDirective } from './upload.directive';
|
||||||
|
|
||||||
|
describe('UploadDirective', () => {
|
||||||
|
|
||||||
|
let directive: UploadDirective;
|
||||||
|
let nativeElement: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nativeElement = {
|
||||||
|
dispatchEvent: () => {}
|
||||||
|
};
|
||||||
|
directive = new UploadDirective(new ElementRef(nativeElement));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be enabled by default', () => {
|
||||||
|
expect(directive.enabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have debug mode switched off by default', () => {
|
||||||
|
expect(directive.debug).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update drag status on dragenter', () => {
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
directive.enabled = true;
|
||||||
|
directive.onDragEnter();
|
||||||
|
expect(directive.isDragging).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update drag status on dragenter when disabled', () => {
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
directive.enabled = false;
|
||||||
|
directive.onDragEnter();
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update drag status on dragover', () => {
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
directive.enabled = true;
|
||||||
|
directive.onDragOver(null);
|
||||||
|
expect(directive.isDragging).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent default event on dragover', () => {
|
||||||
|
let event = new Event('dom-event');
|
||||||
|
spyOn(event, 'preventDefault').and.stub();
|
||||||
|
directive.enabled = true;
|
||||||
|
directive.onDragOver(event);
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
expect(directive.isDragging).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update drag status on dragover when disabled', () => {
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
directive.enabled = false;
|
||||||
|
directive.onDragOver(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update drag status on dragleave', () => {
|
||||||
|
directive.enabled = true;
|
||||||
|
directive.isDragging = true;
|
||||||
|
directive.onDragLeave();
|
||||||
|
expect(directive.isDragging).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update drag status on dragleave when disabled', () => {
|
||||||
|
directive.enabled = false;
|
||||||
|
directive.isDragging = true;
|
||||||
|
directive.onDragLeave();
|
||||||
|
expect(directive.isDragging).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent default event on drop', () => {
|
||||||
|
directive.enabled = true;
|
||||||
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
|
directive.onDrop(<DragEvent>event);
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop default event propagation on drop', () => {
|
||||||
|
directive.enabled = true;
|
||||||
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
|
directive.onDrop(<DragEvent>event);
|
||||||
|
expect(event.stopPropagation).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not prevent default event on drop when disabled', () => {
|
||||||
|
directive.enabled = false;
|
||||||
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
|
directive.onDrop(<DragEvent>event);
|
||||||
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise upload-files event on files drop', () => {
|
||||||
|
directive.enabled = true;
|
||||||
|
let files = [<File> {}];
|
||||||
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
|
spyOn(directive, 'getFilesDropped').and.returnValue(files);
|
||||||
|
spyOn(nativeElement, 'dispatchEvent').and.stub();
|
||||||
|
directive.onDrop(event);
|
||||||
|
expect(nativeElement.dispatchEvent).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide dropped files in upload-files event', () => {
|
||||||
|
directive.enabled = true;
|
||||||
|
let files = [<File> {}];
|
||||||
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
|
spyOn(directive, 'getFilesDropped').and.returnValue(files);
|
||||||
|
|
||||||
|
spyOn(nativeElement, 'dispatchEvent').and.callFake(e => {
|
||||||
|
expect(e.detail.files.length).toBe(1);
|
||||||
|
expect(e.detail.files[0]).toBe(files[0]);
|
||||||
|
});
|
||||||
|
directive.onDrop(event);
|
||||||
|
expect(nativeElement.dispatchEvent).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,113 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Directive, Input, HostBinding, HostListener, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[adf-upload]'
|
||||||
|
})
|
||||||
|
export class UploadDirective {
|
||||||
|
|
||||||
|
@Input('adf-upload')
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
@Input('adf-upload-data')
|
||||||
|
data: any;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
debug: boolean = false;
|
||||||
|
|
||||||
|
@HostBinding('class.adf-upload__dragging')
|
||||||
|
isDragging: boolean;
|
||||||
|
|
||||||
|
constructor(private el: ElementRef) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('dragenter')
|
||||||
|
onDragEnter() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.isDragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('dragover', ['$event'])
|
||||||
|
onDragOver(event: Event) {
|
||||||
|
if (this.enabled) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.isDragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('dragleave')
|
||||||
|
onDragLeave() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.isDragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('drop', ['$event'])
|
||||||
|
onDrop(event: DragEvent) {
|
||||||
|
if (this.enabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.isDragging = false;
|
||||||
|
|
||||||
|
let files = this.getFilesDropped(event.dataTransfer);
|
||||||
|
if (files.length > 0) {
|
||||||
|
let e = new CustomEvent('upload-files', {
|
||||||
|
detail: {
|
||||||
|
sender: this,
|
||||||
|
data: this.data,
|
||||||
|
files: files
|
||||||
|
},
|
||||||
|
bubbles: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.el.nativeElement.dispatchEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract files from the DataTransfer object used to hold the data that is being dragged during a drag and drop operation.
|
||||||
|
* @param dataTransfer DataTransfer object
|
||||||
|
*/
|
||||||
|
protected getFilesDropped(dataTransfer: DataTransfer): File[] {
|
||||||
|
let result: File[] = [];
|
||||||
|
|
||||||
|
if (dataTransfer) {
|
||||||
|
let items: DataTransferItemList = dataTransfer.items;
|
||||||
|
|
||||||
|
if (items && items.length > 0) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item: DataTransferItem = items[i];
|
||||||
|
if (item.type) {
|
||||||
|
let file = item.getAsFile();
|
||||||
|
if (file) {
|
||||||
|
result.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@@ -184,6 +184,7 @@ You can also use HTML-based schema declaration like shown below:
|
|||||||
| `actionsPosition` | string (left\|right) | right | Position of the actions dropdown menu. |
|
| `actionsPosition` | string (left\|right) | right | Position of the actions dropdown menu. |
|
||||||
| `fallbackThumbnail` | string | | Fallback image for row ehre thubnail is missing|
|
| `fallbackThumbnail` | string | | Fallback image for row ehre thubnail is missing|
|
||||||
| `contextMenu` | boolean | false | Toggles custom context menu for the component |
|
| `contextMenu` | boolean | false | Toggles custom context menu for the component |
|
||||||
|
| `allowDropFiles` | boolean | false | Toggle file drop support for rows (see **ng2-alfresco-core/UploadDirective** for more details) |
|
||||||
|
|
||||||
### DataColumn Properties
|
### DataColumn Properties
|
||||||
|
|
||||||
|
@@ -122,3 +122,16 @@
|
|||||||
.alfresco-datatable__row--selected {
|
.alfresco-datatable__row--selected {
|
||||||
color: rgb(68,138,255);
|
color: rgb(68,138,255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.adf-upload__dragging > td {
|
||||||
|
border-top: 1px dashed rgb(68,138,255);
|
||||||
|
border-bottom: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-upload__dragging > td:first-child {
|
||||||
|
border-left: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-upload__dragging > td:last-child {
|
||||||
|
border-right: 1px dashed rgb(68,138,255);
|
||||||
|
}
|
||||||
|
@@ -37,7 +37,8 @@
|
|||||||
|
|
||||||
<tr *ngFor="let row of data.getRows(); let idx = index" tabindex="0"
|
<tr *ngFor="let row of data.getRows(); let idx = index" tabindex="0"
|
||||||
class="alfresco-datatable__row"
|
class="alfresco-datatable__row"
|
||||||
[class.alfresco-datatable__row--selected]="selectedRow === row">
|
[class.alfresco-datatable__row--selected]="selectedRow === row"
|
||||||
|
[adf-upload]="allowDropFiles" [adf-upload-data]="row">
|
||||||
|
|
||||||
<!-- Actions (right) -->
|
<!-- Actions (right) -->
|
||||||
<td *ngIf="actions && actionsPosition === 'left'" class="alfresco-datatable__actions-cell">
|
<td *ngIf="actions && actionsPosition === 'left'" class="alfresco-datatable__actions-cell">
|
||||||
|
@@ -51,6 +51,9 @@ export class DataTableComponent implements AfterContentInit {
|
|||||||
@Input()
|
@Input()
|
||||||
contextMenu: boolean = false;
|
contextMenu: boolean = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
allowDropFiles: boolean = false;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
rowClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
|
rowClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
|
||||||
|
|
||||||
|
@@ -184,9 +184,10 @@ The properties currentFolderId, folderNode and node are the entry initialization
|
|||||||
| `contentActionsPosition` | string (left\|right) | right | Position of the content actions dropdown menu. |
|
| `contentActionsPosition` | string (left\|right) | right | Position of the content actions dropdown menu. |
|
||||||
| `contextMenuActions` | boolean | false | Toggles context menus for each row |
|
| `contextMenuActions` | boolean | false | Toggles context menus for each row |
|
||||||
| `enablePagination` | boolean | true | Shows pagination |
|
| `enablePagination` | boolean | true | Shows pagination |
|
||||||
| `creationMenuActions` | boolean | true | Toggles the creation menu actions|
|
| `creationMenuActions` | boolean | true | Toggles the creation menu actions |
|
||||||
| `rowFilter` | `RowFilter` | | Custom row filter, [see more](#custom-row-filter).
|
| `rowFilter` | `RowFilter` | | Custom row filter, [see more](#custom-row-filter). |
|
||||||
| `imageResolver` | `ImageResolver` | | Custom image resolver, [see more](#custom-image-resolver).
|
| `imageResolver` | `ImageResolver` | | Custom image resolver, [see more](#custom-image-resolver). |
|
||||||
|
| `allowDropFiles` | boolean | false | Toggle file drop support for rows (see **ng2-alfresco-core/UploadDirective** for more details) |
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
[actionsPosition]="contentActionsPosition"
|
[actionsPosition]="contentActionsPosition"
|
||||||
[multiselect]="multiselect"
|
[multiselect]="multiselect"
|
||||||
[fallbackThumbnail]="fallbackThumbnail"
|
[fallbackThumbnail]="fallbackThumbnail"
|
||||||
|
[allowDropFiles]="allowDropFiles"
|
||||||
[contextMenu]="contextMenuActions"
|
[contextMenu]="contextMenuActions"
|
||||||
(showRowContextMenu)="onShowRowContextMenu($event)"
|
(showRowContextMenu)="onShowRowContextMenu($event)"
|
||||||
(showRowActionsMenu)="onShowRowActionsMenu($event)"
|
(showRowActionsMenu)="onShowRowActionsMenu($event)"
|
||||||
|
@@ -82,6 +82,9 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
|
|||||||
@Input()
|
@Input()
|
||||||
emptyFolderImageUrl: string = this.baseComponentPath + 'assets/images/empty_doc_lib.svg';
|
emptyFolderImageUrl: string = this.baseComponentPath + 'assets/images/empty_doc_lib.svg';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
allowDropFiles: boolean = false;
|
||||||
|
|
||||||
skipCount: number = 0;
|
skipCount: number = 0;
|
||||||
|
|
||||||
pagination: Pagination;
|
pagination: Pagination;
|
||||||
|
@@ -128,7 +128,7 @@ export class UploadButtonComponent {
|
|||||||
let directoryName = this.getDirectoryName(directoryPath);
|
let directoryName = this.getDirectoryName(directoryPath);
|
||||||
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
|
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
|
||||||
|
|
||||||
this.uploadService.createFolder(absolutePath, directoryName)
|
this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
res => {
|
res => {
|
||||||
let relativeDir = this.currentFolderPath + '/' + directoryPath;
|
let relativeDir = this.currentFolderPath + '/' + directoryPath;
|
||||||
|
@@ -4,8 +4,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-focus {
|
.file-draggable__input-focus {
|
||||||
color: #2196F3;
|
color: #2196F3;
|
||||||
margin-left: 3px;
|
border: 1px dashed #2196F3;
|
||||||
border: 3px dashed #2196F3;
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
(onFilesDropped)="onFilesDropped($event)"
|
(onFilesDropped)="onFilesDropped($event)"
|
||||||
(onFilesEntityDropped)="onFilesEntityDropped($event)"
|
(onFilesEntityDropped)="onFilesEntityDropped($event)"
|
||||||
(onFolderEntityDropped)="onFolderEntityDropped($event)"
|
(onFolderEntityDropped)="onFolderEntityDropped($event)"
|
||||||
|
(upload-files)="onUploadFiles($event)"
|
||||||
dropzone="" webkitdropzone="*" #droparea>
|
dropzone="" webkitdropzone="*" #droparea>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -72,16 +72,35 @@ export class UploadDragAreaComponent {
|
|||||||
this.uploadService.setOptions(formFields, this.versioning);
|
this.uploadService.setOptions(formFields, this.versioning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles 'upload-files' events raised by child components.
|
||||||
|
* @param e DOM event
|
||||||
|
*/
|
||||||
|
onUploadFiles(e: CustomEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let files = e.detail.files;
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
if (e.detail.data.obj.entry.isFolder) {
|
||||||
|
let id = e.detail.data.obj.entry.id;
|
||||||
|
this.onFilesDropped(files, id, '/');
|
||||||
|
} else {
|
||||||
|
this.onFilesDropped(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when files are dropped in the drag area.
|
* Method called when files are dropped in the drag area.
|
||||||
*
|
*
|
||||||
* @param {File[]} files - files dropped in the drag area.
|
* @param {File[]} files - files dropped in the drag area.
|
||||||
*/
|
*/
|
||||||
onFilesDropped(files: File[]): void {
|
onFilesDropped(files: File[], rootId?: string, directory?: string): void {
|
||||||
if (files.length) {
|
if (files.length) {
|
||||||
if (this.checkValidity(files)) {
|
if (this.checkValidity(files)) {
|
||||||
this.uploadService.addToQueue(files);
|
this.uploadService.addToQueue(files);
|
||||||
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, this.currentFolderPath, this.onSuccess);
|
this.uploadService.uploadFilesInTheQueue(rootId || this.rootFolderId, directory || this.currentFolderPath, this.onSuccess);
|
||||||
let latestFilesAdded = this.uploadService.getQueue();
|
let latestFilesAdded = this.uploadService.getQueue();
|
||||||
if (this.showNotificationBar) {
|
if (this.showNotificationBar) {
|
||||||
this.showUndoNotificationBar(latestFilesAdded);
|
this.showUndoNotificationBar(latestFilesAdded);
|
||||||
@@ -132,7 +151,7 @@ export class UploadDragAreaComponent {
|
|||||||
let relativePath = folder.fullPath.replace(folder.name, '');
|
let relativePath = folder.fullPath.replace(folder.name, '');
|
||||||
relativePath = this.currentFolderPath + relativePath;
|
relativePath = this.currentFolderPath + relativePath;
|
||||||
|
|
||||||
this.uploadService.createFolder(relativePath, folder.name)
|
this.uploadService.createFolder(relativePath, folder.name, this.rootFolderId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
message => {
|
message => {
|
||||||
this.onSuccess.emit({
|
this.onSuccess.emit({
|
||||||
|
@@ -19,7 +19,7 @@ import { FileDraggableDirective } from '../directives/file-draggable.directive';
|
|||||||
|
|
||||||
describe('FileDraggableDirective', () => {
|
describe('FileDraggableDirective', () => {
|
||||||
|
|
||||||
let component;
|
let component: FileDraggableDirective;
|
||||||
|
|
||||||
beforeEach( () => {
|
beforeEach( () => {
|
||||||
component = new FileDraggableDirective();
|
component = new FileDraggableDirective();
|
||||||
@@ -51,7 +51,7 @@ describe('FileDraggableDirective', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
component._onDropFiles(fakeEvent);
|
component.onDropFiles(fakeEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit onFilesDropped event when a file is dragged not with Chrome' , (done) => {
|
it('should emit onFilesDropped event when a file is dragged not with Chrome' , (done) => {
|
||||||
@@ -70,7 +70,7 @@ describe('FileDraggableDirective', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
component._onDropFiles(fakeEvent);
|
component.onDropFiles(fakeEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit onFilesDropped event when a file is dragged with Chrome', (done) => {
|
it('should emit onFilesDropped event when a file is dragged with Chrome', (done) => {
|
||||||
@@ -90,7 +90,7 @@ describe('FileDraggableDirective', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
component._onDropFiles(fakeEvent);
|
component.onDropFiles(fakeEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should take the focus when the drag enter is called', () => {
|
it('should take the focus when the drag enter is called', () => {
|
||||||
@@ -98,7 +98,7 @@ describe('FileDraggableDirective', () => {
|
|||||||
spyOn(mockEvent, 'preventDefault');
|
spyOn(mockEvent, 'preventDefault');
|
||||||
|
|
||||||
expect(component.getInputFocus()).toBe(false);
|
expect(component.getInputFocus()).toBe(false);
|
||||||
component._onDragEnter(mockEvent);
|
component.onDragEnter(mockEvent);
|
||||||
expect(component.getInputFocus()).toBe(true);
|
expect(component.getInputFocus()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, EventEmitter, Output } from '@angular/core';
|
import { Directive, HostListener, HostBinding, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [file-draggable]
|
* [file-draggable]
|
||||||
@@ -29,17 +29,12 @@ import { Directive, EventEmitter, Output } from '@angular/core';
|
|||||||
* @returns {FileDraggableDirective} .
|
* @returns {FileDraggableDirective} .
|
||||||
*/
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[file-draggable]',
|
selector: '[file-draggable]'
|
||||||
host: {
|
|
||||||
'(drop)': '_onDropFiles($event)',
|
|
||||||
'(dragenter)': '_onDragEnter($event)',
|
|
||||||
'(dragleave)': '_onDragLeave($event)',
|
|
||||||
'(dragover)': '_onDragOver($event)',
|
|
||||||
'[class.input-focus]': '_inputFocusClass'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
export class FileDraggableDirective {
|
export class FileDraggableDirective {
|
||||||
|
|
||||||
|
files: File [];
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
onFilesDropped: EventEmitter<any> = new EventEmitter();
|
onFilesDropped: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
@@ -49,41 +44,39 @@ export class FileDraggableDirective {
|
|||||||
@Output()
|
@Output()
|
||||||
onFolderEntityDropped: EventEmitter<any> = new EventEmitter();
|
onFolderEntityDropped: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
files: File [];
|
@HostBinding('class.file-draggable__input-focus')
|
||||||
|
inputFocusClass: boolean = false;
|
||||||
private _inputFocusClass: boolean = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when files is dropped in the drag and drop area.
|
* Method called when files is dropped in the drag and drop area.
|
||||||
*
|
* @param event DOM event.
|
||||||
* @param {$event} $event - DOM $event.
|
|
||||||
*/
|
*/
|
||||||
_onDropFiles($event: any): void {
|
@HostListener('drop', ['$event'])
|
||||||
this._preventDefault($event);
|
onDropFiles(event: any): void {
|
||||||
|
if (!event.defaultPrevented) {
|
||||||
|
this.preventDefault(event);
|
||||||
|
|
||||||
let items = $event.dataTransfer.items;
|
let items = event.dataTransfer.items;
|
||||||
if (items) {
|
if (items) {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
|
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
|
||||||
let item = items[i].webkitGetAsEntry();
|
let item = items[i].webkitGetAsEntry();
|
||||||
if (item) {
|
if (item) {
|
||||||
this._traverseFileTree(item);
|
this.traverseFileTree(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let files = event.dataTransfer.files;
|
||||||
|
this.onFilesDropped.emit(files);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let files = $event.dataTransfer.files;
|
|
||||||
this.onFilesDropped.emit(files);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// safari or FF
|
||||||
|
let files = event.dataTransfer.files;
|
||||||
|
this.onFilesDropped.emit(files);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// safari or FF
|
|
||||||
let files = $event.dataTransfer.files;
|
|
||||||
this.onFilesDropped.emit(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._inputFocusClass = false;
|
this.inputFocusClass = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,7 +84,7 @@ export class FileDraggableDirective {
|
|||||||
*
|
*
|
||||||
* @param {Object} item - can contains files or folders.
|
* @param {Object} item - can contains files or folders.
|
||||||
*/
|
*/
|
||||||
private _traverseFileTree(item: any): void {
|
private traverseFileTree(item: any): void {
|
||||||
if (item.isFile) {
|
if (item.isFile) {
|
||||||
let self = this;
|
let self = this;
|
||||||
self.onFilesEntityDropped.emit(item);
|
self.onFilesEntityDropped.emit(item);
|
||||||
@@ -105,44 +98,50 @@ export class FileDraggableDirective {
|
|||||||
/**
|
/**
|
||||||
* Change the style of the drag area when a file drag in.
|
* Change the style of the drag area when a file drag in.
|
||||||
*
|
*
|
||||||
* @param {$event} $event - DOM $event.
|
* @param {event} event - DOM event.
|
||||||
*/
|
*/
|
||||||
_onDragEnter($event: Event): void {
|
@HostListener('dragenter', ['$event'])
|
||||||
this._preventDefault($event);
|
onDragEnter(event: Event): void {
|
||||||
|
if (!event.defaultPrevented) {
|
||||||
this._inputFocusClass = true;
|
this.preventDefault(event);
|
||||||
|
this.inputFocusClass = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the style of the drag area when a file drag out.
|
* Change the style of the drag area when a file drag out.
|
||||||
*
|
*
|
||||||
* @param {$event} $event - DOM $event.
|
* @param {event} event - DOM event.
|
||||||
*/
|
*/
|
||||||
_onDragLeave($event: Event): void {
|
@HostListener('dragleave', ['$event'])
|
||||||
this._preventDefault($event);
|
onDragLeave(event: Event): void {
|
||||||
|
if (!event.defaultPrevented) {
|
||||||
this._inputFocusClass = false;
|
this.preventDefault(event);
|
||||||
|
this.inputFocusClass = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the style of the drag area when a file is over the drag area.
|
* Change the style of the drag area when a file is over the drag area.
|
||||||
*
|
*
|
||||||
* @param $event
|
* @param event
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_onDragOver($event: Event): void {
|
@HostListener('dragover', ['$event'])
|
||||||
this._preventDefault($event);
|
onDragOver(event: Event): void {
|
||||||
this._inputFocusClass = true;
|
if (!event.defaultPrevented) {
|
||||||
|
this.preventDefault(event);
|
||||||
|
this.inputFocusClass = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent default and stop propagation of the DOM event.
|
* Prevent default and stop propagation of the DOM event.
|
||||||
*
|
*
|
||||||
* @param {$event} $event - DOM $event.
|
* @param {event} $event - DOM event.
|
||||||
*/
|
*/
|
||||||
_preventDefault($event: Event): void {
|
preventDefault(event: Event): void {
|
||||||
$event.stopPropagation();
|
event.stopPropagation();
|
||||||
$event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,6 +149,6 @@ export class FileDraggableDirective {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getInputFocus () {
|
getInputFocus () {
|
||||||
return this._inputFocusClass;
|
return this.inputFocusClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -162,17 +162,14 @@ export class UploadService {
|
|||||||
* Create a folder
|
* Create a folder
|
||||||
* @param name - the folder name
|
* @param name - the folder name
|
||||||
*/
|
*/
|
||||||
createFolder(relativePath: string, name: string) {
|
createFolder(relativePath: string, name: string, parentId?: string) {
|
||||||
return Observable.fromPromise(this.callApiCreateFolder(relativePath, name))
|
return Observable.fromPromise(this.callApiCreateFolder(relativePath, name, parentId))
|
||||||
.map(res => {
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.do(data => this.logService.info('Node data', data)) // eyeball results in the console
|
.do(data => this.logService.info('Node data', data)) // eyeball results in the console
|
||||||
.catch(err => this.handleError(err));
|
.catch(err => this.handleError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
callApiCreateFolder(relativePath: string, name: string): Promise<MinimalNodeEntity> {
|
callApiCreateFolder(relativePath: string, name: string, parentId?: string): Promise<MinimalNodeEntity> {
|
||||||
return this.apiService.getInstance().nodes.createFolder(name, relativePath);
|
return this.apiService.getInstance().nodes.createFolder(name, relativePath, parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user