New packages org (#2639)

New packages org
This commit is contained in:
Eugenio Romano
2017-11-16 14:12:52 +00:00
committed by GitHub
parent 6a24c6ef75
commit a52bb5600a
1984 changed files with 17179 additions and 40423 deletions

View File

@@ -0,0 +1,54 @@
/*!
* @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 { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { HighlightDirective } from './highlight.directive';
import { LogoutDirective } from './logout.directive';
import { NodeDeleteDirective } from './node-delete.directive';
import { NodeFavoriteDirective } from './node-favorite.directive';
import { NodePermissionDirective } from './node-permission.directive';
import { NodeRestoreDirective } from './node-restore.directive';
import { UploadDirective } from './upload.directive';
@NgModule({
imports: [
CommonModule,
MaterialModule
],
declarations: [
HighlightDirective,
LogoutDirective,
NodeDeleteDirective,
NodeFavoriteDirective,
NodePermissionDirective,
NodeRestoreDirective,
UploadDirective
],
exports: [
HighlightDirective,
LogoutDirective,
NodeDeleteDirective,
NodeFavoriteDirective,
NodePermissionDirective,
NodeRestoreDirective,
UploadDirective
]
})
export class DirectiveModule {}

View File

@@ -0,0 +1,124 @@
/*!
* @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 { Component, ViewChildren } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { HighlightTransformService } from '../services/highlight-transform.service';
import { HighlightDirective } from './highlight.directive';
const template: string = `
<div id="outerDiv1" adf-highlight adf-highlight-selector=".highlightable" adf-highlight-class="highlight-for-free-willy">
<div id="innerDiv11" class="highlightable">Lorem ipsum salana-eyong-aysis dolor sit amet</div>
<div id="innerDiv12">Lorem ipsum salana-eyong-aysis dolor sit amet</div>
<div id="innerDiv13" class="highlightable">consectetur adipiscing elit</div>
<div id="innerDiv14" class="highlightable">sed do eiusmod salana-eyong-aysis tempor incididunt</div>
</div>
<div id="outerDiv2" adf-highlight adf-highlight-selector=".highlightable">
<div id="innerDiv21" class="highlightable">Lorem ipsum salana-eyong-aysis dolor sit amet</div>
</div>`;
@Component({ selector: 'adf-test-component', template })
class TestComponent {
@ViewChildren(HighlightDirective) public hightlightDirectives;
}
describe('HighlightDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent
],
providers: [
HighlightTransformService
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should replace the searched text with the default hightlight class in the proper element (adf-highlight-selector)', () => {
component.hightlightDirectives.last.highlight('salana-eyong-aysis');
fixture.detectChanges();
const containerElement = fixture.debugElement.query(By.css('#innerDiv21'));
expect(containerElement).not.toBeNull();
expect(containerElement.nativeElement.innerHTML).toBe('Lorem ipsum <span class="adf-highlight">salana-eyong-aysis</span> dolor sit amet');
});
it('should replace the searched text with the default hightlight class in every proper element (highlight-for-free-willy)', () => {
component.hightlightDirectives.first.highlight('salana-eyong-aysis');
fixture.detectChanges();
const containerElement1 = fixture.debugElement.query(By.css('#innerDiv11'));
const containerElement2 = fixture.debugElement.query(By.css('#innerDiv14'));
expect(containerElement1).not.toBeNull();
expect(containerElement2).not.toBeNull();
expect(containerElement1.nativeElement.innerHTML).toBe('Lorem ipsum <span class="highlight-for-free-willy">salana-eyong-aysis</span> dolor sit amet');
expect(containerElement2.nativeElement.innerHTML).toBe('sed do eiusmod <span class="highlight-for-free-willy">salana-eyong-aysis</span> tempor incididunt');
});
it('should NOT replace the searched text in an element without the proper selector class', () => {
component.hightlightDirectives.first.highlight('salana-eyong-aysis');
fixture.detectChanges();
const containerElement1 = fixture.debugElement.query(By.css('#innerDiv12'));
expect(containerElement1).not.toBeNull();
expect(containerElement1.nativeElement.innerHTML).toBe('Lorem ipsum salana-eyong-aysis dolor sit amet');
});
it('should NOT reinsert the same text to the innerText if there was no change at all (search string is not found)', () => {
const highlighter = TestBed.get(HighlightTransformService);
spyOn(highlighter, 'highlight').and.returnValue({ changed: false, text: 'Modified text' });
component.hightlightDirectives.first.highlight('salana-eyong-aysis');
fixture.detectChanges();
const containerElement = fixture.debugElement.query(By.css('#innerDiv11'));
expect(containerElement).not.toBeNull();
expect(containerElement.nativeElement.innerHTML).not.toContain('Modified text');
});
it('should do the search only if there is a search string presented', () => {
const highlighter = TestBed.get(HighlightTransformService);
spyOn(highlighter, 'highlight').and.callThrough();
component.hightlightDirectives.first.highlight('');
fixture.detectChanges();
expect(highlighter.highlight).not.toHaveBeenCalled();
});
it('should do the search only if there is a node selector string presented', () => {
const highlighter = TestBed.get(HighlightTransformService);
spyOn(highlighter, 'highlight').and.callThrough();
const callback = function() {
component.hightlightDirectives.first.highlight('raddish', '');
fixture.detectChanges();
};
expect(callback).not.toThrowError();
expect(highlighter.highlight).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,52 @@
/*!
* @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, ElementRef, Input, Renderer } from '@angular/core';
import { HighlightTransformService, HightlightTransformResult } from '../services/highlight-transform.service';
@Directive({
selector: '[adf-highlight]'
})
export class HighlightDirective {
@Input('adf-highlight-selector')
selector: string = '';
@Input('adf-highlight')
search: string = '';
@Input('adf-highlight-class')
classToApply: string = 'adf-highlight';
constructor(
private el: ElementRef,
private renderer: Renderer,
private highlightTransformService: HighlightTransformService) { }
public highlight(search = this.search, selector = this.selector, classToApply = this.classToApply) {
if (search && selector) {
const elements = this.el.nativeElement.querySelectorAll(selector);
elements.forEach((element) => {
const result: HightlightTransformResult = this.highlightTransformService.highlight(element.innerHTML, search, classToApply);
if (result.changed) {
this.renderer.setElementProperty(element, 'innerHTML', result.text);
}
});
}
}
}

View File

@@ -0,0 +1,18 @@
/*!
* @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.
*/
export * from './public-api';

View File

@@ -0,0 +1,78 @@
/*!
* @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 { Component } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Rx';
import { AuthenticationService } from '../services';
describe('LogoutDirective', () => {
@Component({
selector: 'adf-test-component',
template: '<button adf-logout></button>'
})
class TestComponent {}
let fixture: ComponentFixture<TestComponent>;
let router: Router;
let authService: AuthenticationService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
TestComponent
]
}).compileComponents();
}));
beforeEach(() => {
router = TestBed.get(Router);
authService = TestBed.get(AuthenticationService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('should redirect to login on click', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]);
});
it('should redirect to login even on logout error', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.throw('err'));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]);
});
});

View File

@@ -0,0 +1,53 @@
/*!
* @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, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
@Directive({
selector: '[adf-logout]'
})
export class LogoutDirective implements OnInit {
constructor(
private elementRef: ElementRef,
private renderer: Renderer2,
private router: Router,
private auth: AuthenticationService) {
}
ngOnInit() {
if (this.elementRef.nativeElement) {
this.renderer.listen(this.elementRef.nativeElement, 'click', (evt) => {
evt.preventDefault();
this.logout();
});
}
}
logout() {
this.auth.logout().subscribe(
() => this.redirectToLogin(),
() => this.redirectToLogin()
);
}
redirectToLogin() {
this.router.navigate(['/login']);
}
}

View File

@@ -0,0 +1,272 @@
/*!
* @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 { Component, DebugElement } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NotificationService } from '../services/notification.service';
import { NodeDeleteDirective } from './node-delete.directive';
@Component({
template: `
<div [adf-delete]="selection"
(delete)="done()">
</div>`
})
class TestComponent {
selection = [];
done = jasmine.createSpy('done');
}
@Component({
template: `
<div [adf-node-permission]="selection" [adf-delete]="selection"
(delete)="done()">
</div>`
})
class TestWithPermissionsComponent {
selection = [];
done = jasmine.createSpy('done');
}
describe('NodeDeleteDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let fixtureWithPermissions: ComponentFixture<TestWithPermissionsComponent>;
let element: DebugElement;
let elementWithPermissions: DebugElement;
let component: TestComponent;
let componentWithPermissions: TestWithPermissionsComponent;
let alfrescoApi: AlfrescoApiService;
let notification: NotificationService;
let nodeApi;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TestWithPermissionsComponent
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
fixtureWithPermissions = TestBed.createComponent(TestWithPermissionsComponent);
component = fixture.componentInstance;
componentWithPermissions = fixtureWithPermissions.componentInstance;
element = fixture.debugElement.query(By.directive(NodeDeleteDirective));
elementWithPermissions = fixtureWithPermissions.debugElement.query(By.directive(NodeDeleteDirective));
alfrescoApi = TestBed.get(AlfrescoApiService);
nodeApi = alfrescoApi.getInstance().nodes;
notification = TestBed.get(NotificationService);
});
}));
describe('Delete', () => {
beforeEach(() => {
spyOn(notification, 'openSnackMessage');
});
it('should do nothing if selection is empty', () => {
spyOn(nodeApi, 'deleteNode');
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(nodeApi.deleteNode).not.toHaveBeenCalled();
});
it('should process node successfully', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
component.selection = <any> [{ entry: { id: '1', name: 'name1' } }];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.SINGULAR'
);
}));
it('should notify failed node deletion', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.reject('error'));
component.selection = [{ entry: { id: '1', name: 'name1' } }];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.ERROR_SINGULAR'
);
}));
it('should notify nodes deletion', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.PLURAL'
);
}));
it('should notify failed nodes deletion', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.reject('error'));
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.ERROR_PLURAL'
);
}));
it('should notify partial deletion when only one node is successful', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.callFake((id) => {
if (id === '1') {
return Promise.reject('error');
} else {
return Promise.resolve();
}
});
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.PARTIAL_SINGULAR'
);
}));
it('should notify partial deletion when some nodes are successful', fakeAsync(() => {
spyOn(nodeApi, 'deleteNode').and.callFake((id) => {
if (id === '1') {
return Promise.reject(null);
}
if (id === '2') {
return Promise.resolve();
}
if (id === '3') {
return Promise.resolve();
}
});
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } },
{ entry: { id: '3', name: 'name3' } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(notification.openSnackMessage).toHaveBeenCalledWith(
'CORE.DELETE_NODE.PARTIAL_PLURAL'
);
}));
it('should emit event when delete is done', fakeAsync(() => {
component.done.calls.reset();
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
component.selection = <any> [{ entry: { id: '1', name: 'name1' } }];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(component.done).toHaveBeenCalled();
}));
it('should disable the button if no node are selected', fakeAsync(() => {
component.selection = [];
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(true);
}));
it('should disable the button if selected node is null', fakeAsync(() => {
component.selection = null;
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(true);
}));
it('should enable the button if nodes are selected', fakeAsync(() => {
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } },
{ entry: { id: '3', name: 'name3' } }
];
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(false);
}));
it('should not enable the button if adf-node-permission is present', fakeAsync(() => {
elementWithPermissions.nativeElement.disabled = false;
componentWithPermissions.selection = [];
fixtureWithPermissions.detectChanges();
componentWithPermissions.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } },
{ entry: { id: '3', name: 'name3' } }
];
fixtureWithPermissions.detectChanges();
expect(elementWithPermissions.nativeElement.disabled).toEqual(false);
}));
});
});

View File

@@ -0,0 +1,217 @@
/*!
* @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, ElementRef, EventEmitter, HostListener, Input, OnChanges, Output } from '@angular/core';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Observable } from 'rxjs/Rx';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NotificationService } from '../services/notification.service';
import { TranslationService } from '../services/translation.service';
interface ProcessedNodeData {
entry: MinimalNodeEntryEntity;
status: number;
}
interface ProcessStatus {
success: ProcessedNodeData[];
failed: ProcessedNodeData[];
someFailed();
someSucceeded();
oneFailed();
oneSucceeded();
allSucceeded();
allFailed();
}
@Directive({
selector: '[adf-delete]'
})
export class NodeDeleteDirective implements OnChanges {
@Input('adf-delete')
selection: MinimalNodeEntity[];
@Input()
permanent: boolean = false;
@Output()
delete: EventEmitter<any> = new EventEmitter();
@HostListener('click')
onClick() {
this.process(this.selection);
}
constructor(private notification: NotificationService,
private alfrescoApiService: AlfrescoApiService,
private translation: TranslationService,
private elementRef: ElementRef) {
}
ngOnChanges() {
if (!this.selection || (this.selection && this.selection.length === 0)) {
this.setDisableAttribute(true);
} else {
if (!this.elementRef.nativeElement.hasAttribute('adf-node-permission')) {
this.setDisableAttribute(false);
}
}
}
private setDisableAttribute(disable: boolean) {
this.elementRef.nativeElement.disabled = disable;
}
private process(selection: MinimalNodeEntity[]) {
if (!selection.length) {
return;
}
const batch = this.getDeleteNodesBatch(selection);
Observable.forkJoin(...batch)
.subscribe((data: ProcessedNodeData[]) => {
const processedItems: ProcessStatus = this.processStatus(data);
this.notify(processedItems);
if (processedItems.someSucceeded) {
this.delete.emit();
}
});
}
private getDeleteNodesBatch(selection: MinimalNodeEntity[]): Observable<ProcessedNodeData>[] {
return selection.map((node) => this.deleteNode(node));
}
private deleteNode(node: MinimalNodeEntity): Observable<ProcessedNodeData> {
const id = (<any> node.entry).nodeId || node.entry.id;
const promise = this.alfrescoApiService.getInstance().nodes.deleteNode(id, {permanent: this.permanent});
return Observable.fromPromise(promise)
.map(() => ({
entry: node.entry,
status: 1
}))
.catch((error: any) => {
return Observable.of({
entry: node.entry,
status: 0
});
});
}
private processStatus(data): ProcessStatus {
const deleteStatus = {
success: [],
failed: [],
get someFailed() {
return !!(this.failed.length);
},
get someSucceeded() {
return !!(this.success.length);
},
get oneFailed() {
return this.failed.length === 1;
},
get oneSucceeded() {
return this.success.length === 1;
},
get allSucceeded() {
return this.someSucceeded && !this.someFailed;
},
get allFailed() {
return this.someFailed && !this.someSucceeded;
}
};
return data.reduce(
(acc, next) => {
if (next.status === 1) {
acc.success.push(next);
} else {
acc.failed.push(next);
}
return acc;
},
deleteStatus
);
}
private notify(status) {
this.getMessage(status).subscribe((message) => this.notification.openSnackMessage(message));
}
private getMessage(status): Observable<string> {
if (status.allFailed && !status.oneFailed) {
return this.translation.get(
'CORE.DELETE_NODE.ERROR_PLURAL',
{number: status.failed.length}
);
}
if (status.allSucceeded && !status.oneSucceeded) {
return this.translation.get(
'CORE.DELETE_NODE.PLURAL',
{number: status.success.length}
);
}
if (status.someFailed && status.someSucceeded && !status.oneSucceeded) {
return this.translation.get(
'CORE.DELETE_NODE.PARTIAL_PLURAL',
{
success: status.success.length,
failed: status.failed.length
}
);
}
if (status.someFailed && status.oneSucceeded) {
return this.translation.get(
'CORE.DELETE_NODE.PARTIAL_SINGULAR',
{
success: status.success.length,
failed: status.failed.length
}
);
}
if (status.oneFailed && !status.someSucceeded) {
return this.translation.get(
'CORE.DELETE_NODE.ERROR_SINGULAR',
{name: status.failed[0].entry.name}
);
}
if (status.oneSucceeded && !status.someFailed) {
return this.translation.get(
'CORE.DELETE_NODE.SINGULAR',
{name: status.success[0].entry.name}
);
}
}
}

View File

@@ -0,0 +1,373 @@
/*!
* @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 { Component, DebugElement } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NodeFavoriteDirective } from './node-favorite.directive';
@Component({
template: `
<div [adf-node-favorite]="selection"
(toggle)="done()">
</div>`
})
class TestComponent {
selection;
done = jasmine.createSpy('done');
}
describe('NodeFavoriteDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let element: DebugElement;
let directiveInstance;
let apiService;
let favoritesApi;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(NodeFavoriteDirective));
directiveInstance = element.injector.get(NodeFavoriteDirective);
apiService = TestBed.get(AlfrescoApiService);
favoritesApi = apiService.getInstance().core.favoritesApi;
});
}));
describe('selection input change event', () => {
it('should not call markFavoritesNodes() if input list is empty', () => {
spyOn(directiveInstance, 'markFavoritesNodes');
component.selection = [];
fixture.detectChanges();
expect(directiveInstance.markFavoritesNodes).not.toHaveBeenCalledWith();
});
it('should call markFavoritesNodes() on input change', () => {
spyOn(directiveInstance, 'markFavoritesNodes');
component.selection = [{ entry: { id: '1', name: 'name1' } }];
fixture.detectChanges();
expect(directiveInstance.markFavoritesNodes).toHaveBeenCalledWith(component.selection);
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '1', name: 'name1' } }
];
fixture.detectChanges();
expect(directiveInstance.markFavoritesNodes).toHaveBeenCalledWith(component.selection);
});
it('should reset favorites if selection is empty', fakeAsync(() => {
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.hasFavorites()).toBe(true);
component.selection = [];
fixture.detectChanges();
tick();
expect(directiveInstance.hasFavorites()).toBe(false);
}));
});
describe('markFavoritesNodes()', () => {
let favoritesApiSpy;
beforeEach(() => {
favoritesApiSpy = spyOn(favoritesApi, 'getFavorite');
});
it('should check each selected node if it is a favorite', fakeAsync(() => {
favoritesApiSpy.and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
tick();
expect(favoritesApiSpy.calls.count()).toBe(2);
}));
it('should not check processed node when another is unselected', fakeAsync(() => {
favoritesApiSpy.and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites.length).toBe(2);
expect(favoritesApiSpy.calls.count()).toBe(2);
favoritesApiSpy.calls.reset();
component.selection = [
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites.length).toBe(1);
expect(favoritesApiSpy).not.toHaveBeenCalled();
}));
it('should not check processed nodes when another is selected', fakeAsync(() => {
favoritesApiSpy.and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites.length).toBe(2);
expect(favoritesApiSpy.calls.count()).toBe(2);
favoritesApiSpy.calls.reset();
component.selection = [
{ entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } },
{ entry: { id: '3', name: 'name3' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites.length).toBe(3);
expect(favoritesApiSpy.calls.count()).toBe(1);
}));
});
describe('toggleFavorite()', () => {
let removeFavoriteSpy;
let addFavoriteSpy;
beforeEach(() => {
removeFavoriteSpy = spyOn(favoritesApi, 'removeFavoriteSite');
addFavoriteSpy = spyOn(favoritesApi, 'addFavorite');
});
afterEach(() => {
removeFavoriteSpy.calls.reset();
addFavoriteSpy.calls.reset();
});
it('should not perform action if favorites collection is empty', () => {
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(removeFavoriteSpy).not.toHaveBeenCalled();
expect(addFavoriteSpy).not.toHaveBeenCalled();
});
it('should call addFavorite() if none is a favorite', fakeAsync(() => {
addFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: false } },
{ entry: { id: '2', name: 'name2', isFavorite: false } }
];
element.triggerEventHandler('click', null);
tick();
expect(addFavoriteSpy.calls.argsFor(0)[1].length).toBe(2);
}));
it('should call addFavorite() on node that is not a favorite in selection', fakeAsync(() => {
addFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFile: true, isFolder: false, isFavorite: false } },
{ entry: { id: '2', name: 'name2', isFile: true, isFolder: false, isFavorite: true } }
];
element.triggerEventHandler('click', null);
tick();
const callArgs = addFavoriteSpy.calls.argsFor(0)[1];
const callParameter = callArgs[0];
expect(callArgs.length).toBe(1);
expect(callParameter.target.file.guid).toBe('1');
}));
it('should call removeFavoriteSite() if all are favorites', fakeAsync(() => {
removeFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: true } },
{ entry: { id: '2', name: 'name2', isFavorite: true } }
];
element.triggerEventHandler('click', null);
tick();
expect(removeFavoriteSpy.calls.count()).toBe(2);
}));
it('should emit event when removeFavoriteSite() is done', fakeAsync(() => {
removeFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: true } }
];
element.triggerEventHandler('click', null);
tick();
expect(component.done).toHaveBeenCalled();
}));
it('should emit event when addFavorite() is done', fakeAsync(() => {
addFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: false } }
];
element.triggerEventHandler('click', null);
tick();
expect(component.done).toHaveBeenCalled();
}));
it('should set isFavorites items to false', fakeAsync(() => {
removeFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: true } }
];
element.triggerEventHandler('click', null);
tick();
expect(directiveInstance.hasFavorites()).toBe(false);
}));
it('should set isFavorites items to true', fakeAsync(() => {
addFavoriteSpy.and.returnValue(Promise.resolve());
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: false } }
];
element.triggerEventHandler('click', null);
tick();
expect(directiveInstance.hasFavorites()).toBe(true);
}));
});
describe('getFavorite()', () => {
it('should process node as favorite', fakeAsync(() => {
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites[0].entry.isFavorite).toBe(true);
}));
it('should not process node as favorite', fakeAsync(() => {
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.reject(null));
component.selection = [
{ entry: { id: '1', name: 'name1' } }
];
fixture.detectChanges();
tick();
expect(directiveInstance.favorites[0].entry.isFavorite).toBe(false);
}));
});
describe('hasFavorites()', () => {
it('should return false when favorites collection is empty', () => {
directiveInstance.favorites = [];
const hasFavorites = directiveInstance.hasFavorites();
expect(hasFavorites).toBe(false);
});
it('should return false when some are not favorite', () => {
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: true } },
{ entry: { id: '2', name: 'name2', isFavorite: false } }
];
const hasFavorites = directiveInstance.hasFavorites();
expect(hasFavorites).toBe(false);
});
it('return true when all are favorite', () => {
directiveInstance.favorites = [
{ entry: { id: '1', name: 'name1', isFavorite: true } },
{ entry: { id: '2', name: 'name2', isFavorite: true } }
];
const hasFavorites = directiveInstance.hasFavorites();
expect(hasFavorites).toBe(true);
});
});
});

View File

@@ -0,0 +1,174 @@
/*!
* @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, EventEmitter, HostListener, Input, OnChanges, Output } from '@angular/core';
import { FavoriteBody, MinimalNodeEntity } from 'alfresco-js-api';
import { Observable } from 'rxjs/Rx';
import { AlfrescoApiService } from '../services/alfresco-api.service';
@Directive({
selector: '[adf-node-favorite]',
exportAs: 'adfFavorite'
})
export class NodeFavoriteDirective implements OnChanges {
private favorites: any[] = [];
@Input('adf-node-favorite')
selection: MinimalNodeEntity[] = [];
@Output() toggle: EventEmitter<any> = new EventEmitter();
@HostListener('click')
onClick() {
this.toggleFavorite();
}
constructor(private alfrescoApiService: AlfrescoApiService) {}
ngOnChanges(changes) {
if (!changes.selection.currentValue.length) {
this.favorites = [];
return;
}
this.markFavoritesNodes(changes.selection.currentValue);
}
toggleFavorite() {
if (!this.favorites.length) {
return;
}
const every = this.favorites.every((selected) => selected.entry.isFavorite);
if (every) {
const batch = this.favorites.map((selected) => {
// shared files have nodeId
const id = selected.entry.nodeId || selected.entry.id;
return Observable.fromPromise(this.alfrescoApiService.getInstance().core.favoritesApi.removeFavoriteSite('-me-', id));
});
Observable.forkJoin(batch).subscribe(() => {
this.favorites.map(selected => selected.entry.isFavorite = false);
this.toggle.emit();
});
}
if (!every) {
const notFavorite = this.favorites.filter((node) => !node.entry.isFavorite);
const body: FavoriteBody[] = notFavorite.map((node) => this.createFavoriteBody(node));
Observable.fromPromise(this.alfrescoApiService.getInstance().core.favoritesApi.addFavorite('-me-', <any> body))
.subscribe(() => {
notFavorite.map(selected => selected.entry.isFavorite = true);
this.toggle.emit();
});
}
}
markFavoritesNodes(selection: MinimalNodeEntity[]) {
if (selection.length <= this.favorites.length) {
const newFavorites = this.reduce(this.favorites, selection);
this.favorites = newFavorites;
}
const result = this.diff(selection, this.favorites);
const batch = this.getProcessBatch(result);
Observable.forkJoin(batch).subscribe((data) => this.favorites.push(...data));
}
hasFavorites(): boolean {
if (this.favorites && !this.favorites.length) {
return false;
}
return this.favorites.every((selected) => selected.entry.isFavorite);
}
private getProcessBatch(selection): any[] {
return selection.map((selected: MinimalNodeEntity) => this.getFavorite(selected));
}
private getFavorite(selected: MinimalNodeEntity): Observable<any> {
const { name, isFile, isFolder } = selected.entry;
// shared files have nodeId
const id = (<any> selected).entry.nodeId || selected.entry.id;
const promise = this.alfrescoApiService.getInstance()
.core.favoritesApi.getFavorite('-me-', id);
return Observable.from(promise)
.map(() => ({
entry: {
id,
isFolder,
isFile,
name,
isFavorite: true
}
}))
.catch(() => {
return Observable.of({
entry: {
id,
isFolder,
isFile,
name,
isFavorite: false
}
});
});
}
private createFavoriteBody(node): FavoriteBody {
const type = this.getNodeType(node);
// shared files have nodeId
const id = node.entry.nodeId || node.entry.id;
return {
target: {
[type]: {
guid: id
}
}
};
}
private getNodeType(node): string {
// shared could only be files
if (!node.entry.isFile && !node.entry.isFolder) {
return 'file';
}
return node.entry.isFile ? 'file' : 'folder';
}
private diff(list, patch): any[] {
const ids = patch.map(item => item.entry.id);
return list.filter(item => ids.includes(item.entry.id) ? null : item);
}
private reduce(patch, comparator): any[] {
const ids = comparator.map(item => item.entry.id);
return patch.filter(item => ids.includes(item.entry.id) ? item : null);
}
}

View File

@@ -0,0 +1,108 @@
/*!
* @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 { Component, DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ContentService } from './../services/content.service';
import { NodePermissionDirective } from './node-permission.directive';
@Component({
template: `
<div [adf-node-permission]="'delete'" [adf-nodes]="selection">
</div>`
})
class TestComponent {
selection = [];
disabled = false;
done = jasmine.createSpy('done');
}
describe('NodePermissionDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let element: DebugElement;
let component: TestComponent;
let alfrescoContentService: ContentService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
alfrescoContentService = TestBed.get(ContentService);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(NodePermissionDirective));
});
}));
it('Should be disabled if no nodes are passed', () => {
component.selection = undefined;
fixture.detectChanges();
component.selection = null;
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(true);
});
it('Should be disabled if nodes is an empty array', () => {
component.selection = null;
fixture.detectChanges();
component.selection = [];
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(true);
});
it('enables element when all nodes have expected permission', () => {
spyOn(alfrescoContentService, 'hasPermission').and.returnValue(true);
component.selection = null;
fixture.detectChanges();
component.selection = <any> [{entry: {id: '1', name: 'name1'}}];
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(false);
});
it('disables element when one of the nodes have no permission', () => {
spyOn(alfrescoContentService, 'hasPermission').and.returnValue(false);
component.selection = null;
fixture.detectChanges();
component.selection = <any> [{entry: {id: '1', name: 'name1'}}];
fixture.detectChanges();
expect(element.nativeElement.disabled).toEqual(true);
});
});

View File

@@ -0,0 +1,77 @@
/*!
* @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, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { ContentService } from './../services/content.service';
export interface NodePermissionSubject {
disabled: boolean;
}
@Directive({
selector: '[adf-node-permission]'
})
export class NodePermissionDirective implements OnChanges {
@Input('adf-node-permission')
permission: string = null;
@Input('adf-nodes')
nodes: MinimalNodeEntity[] = [];
constructor(private elementRef: ElementRef,
private contentService: ContentService) {
}
ngOnChanges(changes: SimpleChanges) {
if (changes.nodes && !changes.nodes.firstChange) {
this.updateElement();
}
}
/**
* Updates disabled state for the decorated element
*
* @memberof NodePermissionDirective
*/
updateElement(): void {
let hasPermission = this.hasPermission(this.nodes, this.permission);
this.setDisableAttribute(!hasPermission);
}
private setDisableAttribute(disable: boolean) {
this.elementRef.nativeElement.disabled = disable;
}
/**
* Checks whether all nodes have a particular permission
*
* @param {MinimalNodeEntity[]} nodes Node collection to check
* @param {string} permission Permission to check for each node
* @returns {boolean} True if all nodes have provided permission, otherwise False
* @memberof NodePermissionDirective
*/
hasPermission(nodes: MinimalNodeEntity[], permission: string): boolean {
if (nodes && nodes.length > 0) {
return nodes.every(node => this.contentService.hasPermission(node.entry, permission));
}
return false;
}
}

View File

@@ -0,0 +1,97 @@
# Node Permission Directive
<!-- markdown-toc start - Don't edit this section. npm run toc to generate it-->
<!-- toc -->
- [Properties](#properties)
- [HTML element example](#html-element-example)
- [Angular component example](#angular-component-example)
* [Implementing the NodePermissionSubject interface](#implementing-the-nodepermissionsubject-interface)
* [Defining your components as an EXTENDIBLE_COMPONENT parent component](#defining-your-components-as-an-extendible_component-parent-component)
<!-- tocstop -->
<!-- markdown-toc end -->
The `NodePermissionDirective` allows you to disable an HTML element or Angular component
by taking a collection of the `MinimalNodeEntity` instances and checking the particular permission.
The decorated element will be disabled if:
- there are no nodes in the collection
- at least one of the nodes has no expected permission
## Properties
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| adf-node-permission | [Permissions](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-alfresco-core/src/models/permissions.enum.ts) | null | Node permission to check (create, delete, update, updatePermissions, !create, !delete, !update, !updatePermissions)|
| adf-nodes | MinimalNodeEntity[] | [] | Nodes to check permission for |
## HTML element example
The best example to show `NodePermissionDirective` in action is by binding DocumentList selection property to a toolbar button.
For example the "Delete" button should be disabled if no selection is present or if user has no rights to delete at least one node in the selection.
```html
<adf-toolbar title="toolbar example">
<button md-icon-button
adf-node-permission="delete"
[adf-nodes]="documentList.selection">
<md-icon>delete</md-icon>
</button>
</adf-toolbar>
<adf-document-list #documentList ...>
...
</adf-document-list>
```
The button will become disabled by default, and is going to change its state once user selects/unselects one or multiple documents that current user has permission to delete.
## Angular component example
You can apply the directive on any angular component which implements the NodePermissionSubject interface. The upload drag area component can be a good candidate, since this one implements that interface. Applying the directive on an angular component is pretty much the same as applying it on an html element.
```html
<alfresco-upload-drag-area
[parentId]="..."
[versioning]="..."
[adf-node-permission]="'create'"
[adf-nodes]="getCurrentDocumentListNode()">
...
</alfresco-upload-drag-area>
```
When designing a component you want to work this directive with, you have two important things to care about.
### Implementing the NodePermissionSubject interface
The component has to implement the NodePermissionSubject interface which basically means it has to have a boolean **disabled** property. This is the property which will be set by the directive.
```js
import { NodePermissionSubject } from 'ng2-alfresco-core';
@Component({...})
export class UploadDragAreaComponent implements NodePermissionSubject {
public disabled: boolean = false;
}
```
### Defining your components as an EXTENDIBLE_COMPONENT parent component
The directive will look up the component in the dependency injection tree, up to at most one step above the current DI level (@Host). Because of this, you have to provide your component with forward referencing as the EXTENDIBLE_COMPONENT.
```js
import { EXTENDIBLE_COMPONENT } from 'ng2-alfresco-core';
@Component({
...
providers: [
{ provide: EXTENDIBLE_COMPONENT, useExisting: forwardRef(() => UploadDragAreaComponent)}
]
})
export class UploadDragAreaComponent implements NodePermissionSubject { ... }
```

View File

@@ -0,0 +1,338 @@
/*!
* @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 { Component, DebugElement } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Rx';
import { TranslationService } from '../services';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NotificationService } from '../services/notification.service';
import { NodeRestoreDirective } from './node-restore.directive';
@Component({
template: `
<div [adf-restore]="selection"
(restore)="done()">
</div>`
})
class TestComponent {
selection = [];
done = jasmine.createSpy('done');
}
describe('NodeRestoreDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let element: DebugElement;
let component: TestComponent;
let alfrescoService: AlfrescoApiService;
let translation: TranslationService;
let notification: NotificationService;
let router: Router;
let nodesService;
let coreApi;
let directiveInstance;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
TestComponent
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(NodeRestoreDirective));
directiveInstance = element.injector.get(NodeRestoreDirective);
alfrescoService = TestBed.get(AlfrescoApiService);
nodesService = alfrescoService.getInstance().nodes;
coreApi = alfrescoService.getInstance().core;
translation = TestBed.get(TranslationService);
notification = TestBed.get(NotificationService);
router = TestBed.get(Router);
});
}));
beforeEach(() => {
spyOn(translation, 'get').and.returnValue(Observable.of('message'));
});
it('should not restore when selection is empty', () => {
spyOn(nodesService, 'restoreNode');
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(nodesService.restoreNode).not.toHaveBeenCalled();
});
it('should not restore nodes when selection has nodes without path', () => {
spyOn(nodesService, 'restoreNode');
component.selection = [ { entry: { id: '1' } } ];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(nodesService.restoreNode).not.toHaveBeenCalled();
});
it('should call restore when selection has nodes with path', fakeAsync(() => {
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
list: { entries: [] }
}));
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(nodesService.restoreNode).toHaveBeenCalled();
}));
describe('refresh()', () => {
it('should reset selection', fakeAsync(() => {
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
list: { entries: [] }
}));
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
fixture.detectChanges();
expect(directiveInstance.selection.length).toBe(1);
element.triggerEventHandler('click', null);
tick();
expect(directiveInstance.selection.length).toBe(0);
}));
it('should reset status', fakeAsync(() => {
directiveInstance.restoreProcessStatus.fail = [{}];
directiveInstance.restoreProcessStatus.success = [{}];
directiveInstance.restoreProcessStatus.reset();
expect(directiveInstance.restoreProcessStatus.fail).toEqual([]);
expect(directiveInstance.restoreProcessStatus.success).toEqual([]);
}));
it('should emit event on finish', fakeAsync(() => {
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
list: { entries: [] }
}));
spyOn(element.nativeElement, 'dispatchEvent');
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(component.done).toHaveBeenCalled();
}));
});
describe('notification', () => {
beforeEach(() => {
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
list: { entries: [] }
}));
});
it('should notify on multiple fails', fakeAsync(() => {
const error = { message: '{ "error": {} }' };
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.callFake((id) => {
if (id === '1') {
return Promise.resolve();
}
if (id === '2') {
return Promise.reject(error);
}
if (id === '3') {
return Promise.reject(error);
}
});
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } },
{ entry: { id: '2', name: 'name2', path: ['somewhere-over-the-rainbow'] } },
{ entry: { id: '3', name: 'name3', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.PARTIAL_PLURAL',
{ number: 2 }
);
}));
it('should notify fail when restored node exist, error 409', fakeAsync(() => {
const error = { message: '{ "error": { "statusCode": 409 } }' };
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.NODE_EXISTS',
{ name: 'name1' }
);
}));
it('should notify fail when restored node returns different statusCode', fakeAsync(() => {
const error = { message: '{ "error": { "statusCode": 404 } }' };
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.GENERIC',
{ name: 'name1' }
);
}));
it('should notify fail when restored node location is missing', fakeAsync(() => {
const error = { message: '{ "error": { } }' };
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.LOCATION_MISSING',
{ name: 'name1' }
);
}));
it('should notify success when restore multiple nodes', fakeAsync(() => {
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.callFake((id) => {
if (id === '1') {
return Promise.resolve();
}
if (id === '2') {
return Promise.resolve();
}
});
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } },
{ entry: { id: '2', name: 'name2', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.PLURAL'
);
}));
it('should notify success on restore selected node', fakeAsync(() => {
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
component.selection = [
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(translation.get).toHaveBeenCalledWith(
'CORE.RESTORE_NODE.SINGULAR',
{ name: 'name1' }
);
}));
it('should navigate to restored node location onAction', fakeAsync(() => {
spyOn(router, 'navigate');
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.of({}) });
component.selection = [
{
entry: {
id: '1',
name: 'name1',
path: {
elements: ['somewhere-over-the-rainbow']
}
}
}
];
fixture.detectChanges();
element.triggerEventHandler('click', null);
tick();
expect(router.navigate).toHaveBeenCalled();
}));
});
});

View File

@@ -0,0 +1,264 @@
/*!
* @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, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { Router } from '@angular/router';
import { DeletedNodeEntry, DeletedNodesPaging, PathInfoEntity } from 'alfresco-js-api';
import { Observable } from 'rxjs/Rx';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NotificationService } from '../services/notification.service';
import { TranslationService } from '../services/translation.service';
@Directive({
selector: '[adf-restore]'
})
export class NodeRestoreDirective {
private restoreProcessStatus;
@Input('adf-restore')
selection: DeletedNodeEntry[];
@Input()
location: string = '';
@Output()
restore: EventEmitter<any> = new EventEmitter();
@HostListener('click')
onClick() {
this.recover(this.selection);
}
constructor(
private alfrescoApiService: AlfrescoApiService,
private translation: TranslationService,
private router: Router,
private notification: NotificationService
) {
this.restoreProcessStatus = this.processStatus();
}
private recover(selection: any) {
if (!selection.length) {
return;
}
const nodesWithPath = this.getNodesWithPath(selection);
if (selection.length && !nodesWithPath.length) {
this.restoreProcessStatus.fail.push(...selection);
this.restoreNotification();
this.refresh();
return;
}
this.restoreNodesBatch(nodesWithPath)
.do((restoredNodes) => {
const status = this.processStatus(restoredNodes);
this.restoreProcessStatus.fail.push(...status.fail);
this.restoreProcessStatus.success.push(...status.success);
})
.flatMap(() => this.getDeletedNodes())
.subscribe(
(deletedNodesList: any) => {
const { entries: nodelist } = deletedNodesList.list;
const { fail: restoreErrorNodes } = this.restoreProcessStatus;
const selectedNodes = this.diff(restoreErrorNodes, selection, false);
const remainingNodes = this.diff(selectedNodes, nodelist);
if (!remainingNodes.length) {
this.restoreNotification();
this.refresh();
} else {
this.recover(remainingNodes);
}
}
);
}
private restoreNodesBatch(batch: DeletedNodeEntry[]): Observable<DeletedNodeEntry[]> {
return Observable.forkJoin(batch.map((node) => this.restoreNode(node)));
}
private getNodesWithPath(selection): DeletedNodeEntry[] {
return selection.filter((node) => node.entry.path);
}
private getDeletedNodes(): Observable<DeletedNodesPaging> {
const promise = this.alfrescoApiService.getInstance()
.core.nodesApi.getDeletedNodes({ include: [ 'path' ] });
return Observable.from(promise);
}
private restoreNode(node): Observable<any> {
const { entry } = node;
const promise = this.alfrescoApiService.getInstance().nodes.restoreNode(entry.id);
return Observable.from(promise)
.map(() => ({
status: 1,
entry
}))
.catch((error) => {
const { statusCode } = (JSON.parse(error.message)).error;
return Observable.of({
status: 0,
statusCode,
entry
});
});
}
private navigateLocation(path: PathInfoEntity) {
const parent = path.elements[path.elements.length - 1];
this.router.navigate([ this.location, parent.id ]);
}
private diff(selection , list, fromList = true): any {
const ids = selection.map(item => item.entry.id);
return list.filter(item => {
if (fromList) {
return ids.includes(item.entry.id) ? item : null;
} else {
return !ids.includes(item.entry.id) ? item : null;
}
});
}
private processStatus(data = []): any {
const status = {
fail: [],
success: [],
get someFailed() {
return !!(this.fail.length);
},
get someSucceeded() {
return !!(this.success.length);
},
get oneFailed() {
return this.fail.length === 1;
},
get oneSucceeded() {
return this.success.length === 1;
},
get allSucceeded() {
return this.someSucceeded && !this.someFailed;
},
get allFailed() {
return this.someFailed && !this.someSucceeded;
},
reset() {
this.fail = [];
this.success = [];
}
};
return data.reduce(
(acc, node) => {
if (node.status) {
acc.success.push(node);
} else {
acc.fail.push(node);
}
return acc;
},
status
);
}
private getRestoreMessage(): Observable<string|any> {
const { restoreProcessStatus: status } = this;
if (status.someFailed && !status.oneFailed) {
return this.translation.get(
'CORE.RESTORE_NODE.PARTIAL_PLURAL',
{
number: status.fail.length
}
);
}
if (status.oneFailed && status.fail[0].statusCode) {
if (status.fail[0].statusCode === 409) {
return this.translation.get(
'CORE.RESTORE_NODE.NODE_EXISTS',
{
name: status.fail[0].entry.name
}
);
} else {
return this.translation.get(
'CORE.RESTORE_NODE.GENERIC',
{
name: status.fail[0].entry.name
}
);
}
}
if (status.oneFailed && !status.fail[0].statusCode) {
return this.translation.get(
'CORE.RESTORE_NODE.LOCATION_MISSING',
{
name: status.fail[0].entry.name
}
);
}
if (status.allSucceeded && !status.oneSucceeded) {
return this.translation.get('CORE.RESTORE_NODE.PLURAL');
}
if (status.allSucceeded && status.oneSucceeded) {
return this.translation.get(
'CORE.RESTORE_NODE.SINGULAR',
{
name: status.success[0].entry.name
}
);
}
}
private restoreNotification(): void {
const status = Object.assign({}, this.restoreProcessStatus);
Observable.zip(
this.getRestoreMessage(),
this.translation.get('CORE.RESTORE_NODE.VIEW')
).subscribe((messages) => {
const [ message, actionLabel ] = messages;
const action = (status.oneSucceeded && !status.someFailed) ? actionLabel : '';
this.notification.openSnackMessageAction(message, action)
.onAction()
.subscribe(() => this.navigateLocation(status.success[0].entry.path));
});
}
private refresh(): void {
this.restoreProcessStatus.reset();
this.selection = [];
this.restore.emit();
}
}

View File

@@ -0,0 +1,26 @@
/*!
* @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.
*/
export * from './highlight.directive';
export * from './logout.directive';
export * from './node-delete.directive';
export * from './node-favorite.directive';
export * from './node-permission.directive';
export * from './node-restore.directive';
export * from './upload.directive';
export * from './directive.module';

View File

@@ -0,0 +1,152 @@
/*!
* @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 { FileInfo } from './../utils/file-utils';
import { UploadDirective } from './upload.directive';
describe('UploadDirective', () => {
let directive: UploadDirective;
let nativeElement: any;
beforeEach(() => {
nativeElement = {
classList: jasmine.createSpyObj('classList', ['add', 'remove']),
dispatchEvent: () => {}
};
directive = new UploadDirective(new ElementRef(nativeElement), null, null);
});
it('should be enabled by default', () => {
expect(directive.enabled).toBeTruthy();
});
it('should update drag status on dragenter', () => {
expect(directive.isDragging).toBeFalsy();
directive.enabled = true;
directive.onDragEnter(null);
expect(directive.isDragging).toBeTruthy();
});
it('should not update drag status on dragenter when disabled', () => {
expect(directive.isDragging).toBeFalsy();
directive.enabled = false;
directive.onDragEnter(null);
expect(directive.isDragging).toBeFalsy();
});
it('should update drag status on dragover', () => {
expect(directive.isDragging).toBeFalsy();
directive.enabled = true;
directive.onDragOver(new CustomEvent('dragover'));
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(new CustomEvent('dragover'));
});
it('should update drag status on dragleave', () => {
directive.enabled = true;
directive.isDragging = true;
directive.onDragLeave(null);
expect(directive.isDragging).toBeFalsy();
});
it('should not update drag status on dragleave when disabled', () => {
directive.enabled = false;
directive.isDragging = true;
directive.onDragLeave(null);
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', (done) => {
directive.enabled = true;
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
spyOn(directive, 'getDataTransfer').and.returnValue({});
spyOn(directive, 'getFilesDropped').and.returnValue(Promise.resolve([
<FileInfo> {},
<FileInfo> {}
]));
spyOn(nativeElement, 'dispatchEvent').and.callFake(_ => {
done();
});
directive.onDrop(event);
});
it('should provide dropped files in upload-files event', (done) => {
directive.enabled = true;
let files = [
<FileInfo> {}
];
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
spyOn(directive, 'getDataTransfer').and.returnValue({});
spyOn(directive, 'getFilesDropped').and.returnValue(Promise.resolve(files));
spyOn(nativeElement, 'dispatchEvent').and.callFake(e => {
expect(e.detail.files.length).toBe(1);
expect(e.detail.files[0]).toBe(files[0]);
done();
});
directive.onDrop(event);
});
it('should reset input value after file upload', () => {
directive.enabled = true;
directive.mode = ['click'];
const files = [
<FileInfo> {}
];
const event = {'currentTarget': {'files': files}, 'target': {'value': '/testpath/document.pdf'}};
directive.onSelectFiles(event);
expect(event.target.value).toBe('');
});
});

View File

@@ -0,0 +1,250 @@
/*!
* @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, ElementRef, HostListener, Input, NgZone, OnDestroy, OnInit, Renderer } from '@angular/core';
import { FileInfo, FileUtils } from '../utils/file-utils';
@Directive({
selector: '[adf-upload]'
})
export class UploadDirective implements OnInit, OnDestroy {
@Input('adf-upload')
enabled: boolean = true;
@Input('adf-upload-data')
data: any;
@Input()
mode: string[] = ['drop']; // click|drop
@Input()
multiple: boolean;
@Input()
accept: string;
@Input()
directory: boolean;
isDragging: boolean = false;
private cssClassName: string = 'adf-upload__dragging';
private upload: HTMLInputElement;
private element: HTMLElement;
constructor(private el: ElementRef, private renderer: Renderer, private ngZone: NgZone) {
this.element = el.nativeElement;
}
ngOnInit() {
if (this.isClickMode() && this.renderer) {
this.upload = this.renderer.createElement(this.el.nativeElement.parentNode, 'input') as HTMLInputElement;
this.upload.type = 'file';
this.upload.style.display = 'none';
this.upload.addEventListener('change', e => this.onSelectFiles(e));
if (this.multiple) {
this.upload.setAttribute('multiple', '');
}
if (this.accept) {
this.upload.setAttribute('accept', this.accept);
}
if (this.directory) {
this.upload.setAttribute('webkitdirectory', '');
}
}
if (this.isDropMode()) {
this.ngZone.runOutsideAngular(() => {
this.element.addEventListener('dragenter', this.onDragEnter.bind(this));
this.element.addEventListener('dragover', this.onDragOver.bind(this));
this.element.addEventListener('dragleave', this.onDragLeave.bind(this));
this.element.addEventListener('drop', this.onDrop.bind(this));
});
}
}
ngOnDestroy() {
this.element.removeEventListener('dragenter', this.onDragEnter);
this.element.removeEventListener('dragover', this.onDragOver);
this.element.removeEventListener('dragleave', this.onDragLeave);
this.element.removeEventListener('drop', this.onDrop);
}
@HostListener('click', ['$event'])
onClick(event: Event) {
if (this.isClickMode() && this.upload) {
event.preventDefault();
this.upload.click();
}
}
onDragEnter(event: Event) {
if (this.isDropMode()) {
this.element.classList.add(this.cssClassName);
this.isDragging = true;
}
}
onDragOver(event: Event) {
event.preventDefault();
if (this.isDropMode()) {
this.element.classList.add(this.cssClassName);
this.isDragging = true;
}
return false;
}
onDragLeave(event) {
if (this.isDropMode()) {
this.element.classList.remove(this.cssClassName);
this.isDragging = false;
}
}
onDrop(event: Event) {
if (this.isDropMode()) {
event.stopPropagation();
event.preventDefault();
this.element.classList.remove(this.cssClassName);
this.isDragging = false;
const dataTranfer = this.getDataTransfer(event);
if (dataTranfer) {
this.getFilesDropped(dataTranfer).then(files => {
this.onUploadFiles(files);
});
}
}
return false;
}
onUploadFiles(files: FileInfo[]) {
if (this.enabled && files.length > 0) {
let e = new CustomEvent('upload-files', {
detail: {
sender: this,
data: this.data,
files: files
},
bubbles: true
});
this.el.nativeElement.dispatchEvent(e);
}
}
protected hasMode(mode: string): boolean {
return this.enabled && mode && this.mode && this.mode.indexOf(mode) > -1;
}
protected isDropMode(): boolean {
return this.hasMode('drop');
}
protected isClickMode(): boolean {
return this.hasMode('click');
}
getDataTransfer(event: Event | any): DataTransfer {
if (event && event.dataTransfer) {
return event.dataTransfer;
}
if (event && event.originalEvent && event.originalEvent.dataTransfer) {
return event.originalEvent.dataTransfer;
}
return null;
}
/**
* 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
*/
getFilesDropped(dataTransfer: DataTransfer): Promise<FileInfo[]> {
return new Promise(resolve => {
const iterations = [];
if (dataTransfer) {
const items = dataTransfer.items;
if (items) {
for (let i = 0; i < items.length; i++) {
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
let item = items[i].webkitGetAsEntry();
if (item) {
if (item.isFile) {
iterations.push(Promise.resolve(<FileInfo> {
entry: item,
file: items[i].getAsFile(),
relativeFolder: '/'
}));
} else if (item.isDirectory) {
iterations.push(new Promise(resolveFolder => {
FileUtils.flattern(item).then(files => resolveFolder(files));
}));
}
}
} else {
iterations.push(Promise.resolve(<FileInfo> {
entry: null,
file: items[i].getAsFile(),
relativeFolder: '/'
}));
}
}
} else {
// safari or FF
let files = FileUtils
.toFileArray(dataTransfer.files)
.map(file => <FileInfo> {
entry: null,
file: file,
relativeFolder: '/'
});
iterations.push(Promise.resolve(files));
}
}
Promise.all(iterations).then(result => {
resolve(result.reduce((a, b) => a.concat(b), []));
});
});
}
/**
* Invoked when user selects files or folders by means of File Dialog
* @param e DOM event
*/
onSelectFiles(e: any): void {
if (this.isClickMode()) {
const input = (<HTMLInputElement> e.currentTarget);
const files = FileUtils.toFileArray(input.files);
this.onUploadFiles(files.map(file => <FileInfo> {
entry: null,
file: file,
relativeFolder: '/'
}));
e.target.value = '';
}
}
}