mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { DataTableModule } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
import { ContentActionModel } from './../../models/content-action.model';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { ContentActionListComponent } from './content-action-list.component';
|
||||
|
||||
describe('ContentColumnList', () => {
|
||||
|
||||
let documentList: DocumentListComponent;
|
||||
let actionList: ContentActionListComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
providers: [
|
||||
DocumentListService
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
actionList = new ContentActionListComponent(documentList);
|
||||
});
|
||||
|
||||
it('should register action', () => {
|
||||
spyOn(documentList.actions, 'push').and.callThrough();
|
||||
|
||||
let action = new ContentActionModel();
|
||||
let result = actionList.registerAction(action);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(documentList.actions.push).toHaveBeenCalledWith(action);
|
||||
});
|
||||
|
||||
it('should require document list instance to register action', () => {
|
||||
actionList = new ContentActionListComponent(null);
|
||||
let action = new ContentActionModel();
|
||||
expect(actionList.registerAction(action)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require action instance to register', () => {
|
||||
spyOn(documentList.actions, 'push').and.callThrough();
|
||||
let result = actionList.registerAction(null);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
expect(documentList.actions.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { ContentActionModel } from './../../models/content-action.model';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'content-actions',
|
||||
template: ''
|
||||
})
|
||||
export class ContentActionListComponent {
|
||||
|
||||
constructor(private documentList: DocumentListComponent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers action handler within the parent document list component.
|
||||
* @param action Action model to register.
|
||||
*/
|
||||
registerAction(action: ContentActionModel): boolean {
|
||||
if (this.documentList && action) {
|
||||
this.documentList.actions.push(action);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,294 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { ContentService, TranslationService, NotificationService } from '@alfresco/core';
|
||||
import { DataTableModule } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
import { FileNode } from '../../../mock';
|
||||
import { ContentActionHandler } from './../../models/content-action.model';
|
||||
import { DocumentActionsService } from './../../services/document-actions.service';
|
||||
import { FolderActionsService } from './../../services/folder-actions.service';
|
||||
import { NodeActionsService } from './../../services/node-actions.service';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { ContentActionListComponent } from './content-action-list.component';
|
||||
import { ContentActionComponent } from './content-action.component';
|
||||
|
||||
describe('ContentAction', () => {
|
||||
|
||||
let documentList: DocumentListComponent;
|
||||
let actionList: ContentActionListComponent;
|
||||
let documentActions: DocumentActionsService;
|
||||
let folderActions: FolderActionsService;
|
||||
|
||||
let contentService: ContentService;
|
||||
let translateService: TranslationService;
|
||||
let notificationService: NotificationService;
|
||||
let nodeActionsService: NodeActionsService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
providers: [
|
||||
DocumentListService
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
contentService = TestBed.get(ContentService);
|
||||
translateService = <TranslationService> { addTranslationFolder: () => {}};
|
||||
nodeActionsService = new NodeActionsService(null, null, null);
|
||||
notificationService = new NotificationService(null);
|
||||
documentActions = new DocumentActionsService(nodeActionsService);
|
||||
folderActions = new FolderActionsService(nodeActionsService, null, contentService);
|
||||
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
actionList = new ContentActionListComponent(documentList);
|
||||
});
|
||||
|
||||
it('should register within parent actions list', () => {
|
||||
spyOn(actionList, 'registerAction').and.stub();
|
||||
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
action.ngOnInit();
|
||||
|
||||
expect(actionList.registerAction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should setup and register model', () => {
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
action.target = 'document';
|
||||
action.title = '<title>';
|
||||
action.icon = '<icon>';
|
||||
|
||||
expect(documentList.actions.length).toBe(0);
|
||||
action.ngOnInit();
|
||||
|
||||
expect(documentList.actions.length).toBe(1);
|
||||
|
||||
let model = documentList.actions[0];
|
||||
expect(model.target).toBe(action.target);
|
||||
expect(model.title).toBe(action.title);
|
||||
expect(model.icon).toBe(action.icon);
|
||||
});
|
||||
|
||||
it('should get action handler from document actions service', () => {
|
||||
|
||||
let handler = function () {
|
||||
};
|
||||
spyOn(documentActions, 'getHandler').and.returnValue(handler);
|
||||
|
||||
let action = new ContentActionComponent(actionList, documentActions, null);
|
||||
action.target = 'document';
|
||||
action.handler = '<handler>';
|
||||
action.ngOnInit();
|
||||
|
||||
expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler);
|
||||
expect(documentList.actions.length).toBe(1);
|
||||
|
||||
let model = documentList.actions[0];
|
||||
expect(model.handler).toBe(handler);
|
||||
});
|
||||
|
||||
it('should get action handler from folder actions service', () => {
|
||||
let handler = function () {
|
||||
};
|
||||
spyOn(folderActions, 'getHandler').and.returnValue(handler);
|
||||
|
||||
let action = new ContentActionComponent(actionList, null, folderActions);
|
||||
action.target = 'folder';
|
||||
action.handler = '<handler>';
|
||||
action.ngOnInit();
|
||||
|
||||
expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler);
|
||||
expect(documentList.actions.length).toBe(1);
|
||||
|
||||
let model = documentList.actions[0];
|
||||
expect(model.handler).toBe(handler);
|
||||
});
|
||||
|
||||
it('should require target to get system handler', () => {
|
||||
spyOn(folderActions, 'getHandler').and.stub();
|
||||
spyOn(documentActions, 'getHandler').and.stub();
|
||||
|
||||
let action = new ContentActionComponent(actionList, documentActions, folderActions);
|
||||
action.handler = '<handler>';
|
||||
|
||||
action.ngOnInit();
|
||||
expect(documentList.actions.length).toBe(1);
|
||||
expect(folderActions.getHandler).not.toHaveBeenCalled();
|
||||
expect(documentActions.getHandler).not.toHaveBeenCalled();
|
||||
|
||||
action.target = 'document';
|
||||
action.ngOnInit();
|
||||
expect(documentActions.getHandler).toHaveBeenCalled();
|
||||
|
||||
action.target = 'folder';
|
||||
action.ngOnInit();
|
||||
expect(folderActions.getHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be case insensitive for document target', () => {
|
||||
spyOn(documentActions, 'getHandler').and.stub();
|
||||
|
||||
let action = new ContentActionComponent(actionList, documentActions, null);
|
||||
action.target = 'DoCuMeNt';
|
||||
action.handler = '<handler>';
|
||||
|
||||
action.ngOnInit();
|
||||
expect(documentActions.getHandler).toHaveBeenCalledWith(action.handler);
|
||||
});
|
||||
|
||||
it('should be case insensitive for folder target', () => {
|
||||
spyOn(folderActions, 'getHandler').and.stub();
|
||||
|
||||
let action = new ContentActionComponent(actionList, null, folderActions);
|
||||
action.target = 'FoLdEr';
|
||||
action.handler = '<handler>';
|
||||
|
||||
action.ngOnInit();
|
||||
expect(folderActions.getHandler).toHaveBeenCalledWith(action.handler);
|
||||
});
|
||||
|
||||
it('should use custom "execute" emitter', (done) => {
|
||||
let emitter = new EventEmitter();
|
||||
|
||||
emitter.subscribe(e => {
|
||||
expect(e.value).toBe('<obj>');
|
||||
done();
|
||||
});
|
||||
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
action.target = 'document';
|
||||
action.execute = emitter;
|
||||
|
||||
action.ngOnInit();
|
||||
expect(documentList.actions.length).toBe(1);
|
||||
|
||||
let model = documentList.actions[0];
|
||||
model.execute('<obj>');
|
||||
});
|
||||
|
||||
it('should sync localizable fields with model', () => {
|
||||
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
action.title = 'title1';
|
||||
action.ngOnInit();
|
||||
|
||||
expect(action.model.title).toBe(action.title);
|
||||
|
||||
action.title = 'title2';
|
||||
action.ngOnChanges(null);
|
||||
|
||||
expect(action.model.title).toBe('title2');
|
||||
});
|
||||
|
||||
it('should not find document action handler with missing service', () => {
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
expect(action.getSystemHandler('document', 'name')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not find folder action handler with missing service', () => {
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
expect(action.getSystemHandler('folder', 'name')).toBeNull();
|
||||
});
|
||||
|
||||
it('should find document action handler via service', () => {
|
||||
let handler = <ContentActionHandler> function (obj: any, target?: any) {
|
||||
};
|
||||
let action = new ContentActionComponent(actionList, documentActions, null);
|
||||
spyOn(documentActions, 'getHandler').and.returnValue(handler);
|
||||
expect(action.getSystemHandler('document', 'name')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should find folder action handler via service', () => {
|
||||
let handler = <ContentActionHandler> function (obj: any, target?: any) {
|
||||
};
|
||||
let action = new ContentActionComponent(actionList, null, folderActions);
|
||||
spyOn(folderActions, 'getHandler').and.returnValue(handler);
|
||||
expect(action.getSystemHandler('folder', 'name')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should not find actions for unknown target type', () => {
|
||||
spyOn(folderActions, 'getHandler').and.stub();
|
||||
spyOn(documentActions, 'getHandler').and.stub();
|
||||
|
||||
let action = new ContentActionComponent(actionList, documentActions, folderActions);
|
||||
|
||||
expect(action.getSystemHandler('unknown', 'name')).toBeNull();
|
||||
expect(folderActions.getHandler).not.toHaveBeenCalled();
|
||||
expect(documentActions.getHandler).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it('should wire model with custom event handler', (done) => {
|
||||
let action = new ContentActionComponent(actionList, documentActions, folderActions);
|
||||
let file = new FileNode();
|
||||
|
||||
let handler = new EventEmitter();
|
||||
handler.subscribe((e) => {
|
||||
expect(e.value).toBe(file);
|
||||
done();
|
||||
});
|
||||
|
||||
action.execute = handler;
|
||||
|
||||
action.ngOnInit();
|
||||
action.model.execute(file);
|
||||
});
|
||||
|
||||
it('should allow registering model without handler', () => {
|
||||
let action = new ContentActionComponent(actionList, documentActions, folderActions);
|
||||
|
||||
spyOn(actionList, 'registerAction').and.callThrough();
|
||||
action.execute = null;
|
||||
action.ngOnInit();
|
||||
|
||||
expect(action.model.handler).toBeUndefined();
|
||||
expect(actionList.registerAction).toHaveBeenCalledWith(action.model);
|
||||
});
|
||||
|
||||
it('should register on init', () => {
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
spyOn(action, 'register').and.callThrough();
|
||||
|
||||
action.ngOnInit();
|
||||
expect(action.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require action list to register action with', () => {
|
||||
let action = new ContentActionComponent(actionList, null, null);
|
||||
expect(action.register()).toBeTruthy();
|
||||
|
||||
action = new ContentActionComponent(null, null, null);
|
||||
expect(action.register()).toBeFalsy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,159 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { ContentActionHandler } from '../../models/content-action.model';
|
||||
import { DocumentActionsService } from '../../services/document-actions.service';
|
||||
import { FolderActionsService } from '../../services/folder-actions.service';
|
||||
import { ContentActionModel } from './../../models/content-action.model';
|
||||
import { ContentActionListComponent } from './content-action-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'content-action',
|
||||
template: '',
|
||||
providers: [
|
||||
DocumentActionsService,
|
||||
FolderActionsService
|
||||
]
|
||||
})
|
||||
export class ContentActionComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input()
|
||||
title: string = 'Action';
|
||||
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
handler: string;
|
||||
|
||||
@Input()
|
||||
target: string;
|
||||
|
||||
@Input()
|
||||
permission: string;
|
||||
|
||||
@Input()
|
||||
disableWithNoPermission: boolean;
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false;
|
||||
|
||||
@Output()
|
||||
execute = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
permissionEvent = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
error = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
success = new EventEmitter();
|
||||
|
||||
model: ContentActionModel;
|
||||
|
||||
constructor(
|
||||
private list: ContentActionListComponent,
|
||||
private documentActions: DocumentActionsService,
|
||||
private folderActions: FolderActionsService) {
|
||||
this.model = new ContentActionModel();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.model = new ContentActionModel({
|
||||
title: this.title,
|
||||
icon: this.icon,
|
||||
permission: this.permission,
|
||||
disableWithNoPermission: this.disableWithNoPermission,
|
||||
target: this.target,
|
||||
disabled: this.disabled
|
||||
});
|
||||
|
||||
if (this.handler) {
|
||||
this.model.handler = this.getSystemHandler(this.target, this.handler);
|
||||
}
|
||||
|
||||
if (this.execute) {
|
||||
this.model.execute = (value: any): void => {
|
||||
this.execute.emit({ value });
|
||||
};
|
||||
}
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
register(): boolean {
|
||||
if (this.list) {
|
||||
return this.list.registerAction(this.model);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnChanges(changes) {
|
||||
// update localizable properties
|
||||
this.model.title = this.title;
|
||||
}
|
||||
|
||||
getSystemHandler(target: string, name: string): ContentActionHandler {
|
||||
if (target) {
|
||||
let ltarget = target.toLowerCase();
|
||||
|
||||
if (ltarget === 'document') {
|
||||
if (this.documentActions) {
|
||||
this.documentActions.permissionEvent.subscribe((permision) => {
|
||||
this.permissionEvent.emit(permision);
|
||||
});
|
||||
|
||||
this.documentActions.error.subscribe((errors) => {
|
||||
this.error.emit(errors);
|
||||
});
|
||||
|
||||
this.documentActions.success.subscribe((message) => {
|
||||
this.success.emit(message);
|
||||
});
|
||||
|
||||
return this.documentActions.getHandler(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ltarget === 'folder') {
|
||||
if (this.folderActions) {
|
||||
this.folderActions.permissionEvent.subscribe((permision) => {
|
||||
this.permissionEvent.emit(permision);
|
||||
});
|
||||
|
||||
this.folderActions.error.subscribe((errors) => {
|
||||
this.error.emit(errors);
|
||||
});
|
||||
|
||||
this.folderActions.success.subscribe((message) => {
|
||||
this.success.emit(message);
|
||||
});
|
||||
|
||||
return this.folderActions.getHandler(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { DataColumn, DataTableModule } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
|
||||
import { LogService } from '@alfresco/core';
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { ContentColumnListComponent } from './content-column-list.component';
|
||||
|
||||
describe('ContentColumnList', () => {
|
||||
|
||||
let documentList: DocumentListComponent;
|
||||
let columnList: ContentColumnListComponent;
|
||||
let logService: LogService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
providers: [
|
||||
DocumentListService,
|
||||
LogService
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
logService = TestBed.get(LogService);
|
||||
columnList = new ContentColumnListComponent(documentList, logService);
|
||||
|
||||
documentList.ngOnInit();
|
||||
});
|
||||
|
||||
it('should register column within parent document list', () => {
|
||||
let columns = documentList.data.getColumns();
|
||||
expect(columns.length).toBe(0);
|
||||
|
||||
let column = <DataColumn> {};
|
||||
let result = columnList.registerColumn(column);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(columns.length).toBe(1);
|
||||
expect(columns[0]).toBe(column);
|
||||
});
|
||||
|
||||
it('should require document list instance to register action', () => {
|
||||
columnList = new ContentColumnListComponent(null, logService);
|
||||
let col = <DataColumn> {};
|
||||
expect(columnList.registerColumn(col)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require action instance to register', () => {
|
||||
spyOn(documentList.actions, 'push').and.callThrough();
|
||||
let result = columnList.registerColumn(null);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
expect(documentList.actions.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,48 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { DataColumn } from '@alfresco/core';
|
||||
import { LogService } from '@alfresco/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'content-columns',
|
||||
template: ''
|
||||
})
|
||||
export class ContentColumnListComponent {
|
||||
|
||||
constructor(private documentList: DocumentListComponent, private logService: LogService ) {
|
||||
this.logService.log('ContentColumnListComponent is deprecated starting with 1.7.0 and may be removed in future versions. Use DataColumnListComponent instead.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers column model within the parent document list component.
|
||||
* @param column Column definition model to register.
|
||||
*/
|
||||
registerColumn(column: DataColumn): boolean {
|
||||
if (this.documentList && column) {
|
||||
let columns = this.documentList.data.getColumns();
|
||||
columns.push(column);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { LogService } from '@alfresco/core';
|
||||
import { DataTableModule } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { ContentColumnListComponent } from './content-column-list.component';
|
||||
import { ContentColumnComponent } from './content-column.component';
|
||||
|
||||
describe('ContentColumn', () => {
|
||||
|
||||
let documentList: DocumentListComponent;
|
||||
let columnList: ContentColumnListComponent;
|
||||
let logService: LogService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
providers: [
|
||||
DocumentListService,
|
||||
LogService
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
logService = TestBed.get(LogService);
|
||||
columnList = new ContentColumnListComponent(documentList, logService);
|
||||
|
||||
documentList.ngOnInit();
|
||||
});
|
||||
|
||||
it('should register model within parent column list', () => {
|
||||
spyOn(columnList, 'registerColumn').and.callThrough();
|
||||
|
||||
let column = new ContentColumnComponent(columnList, logService);
|
||||
column.ngAfterContentInit();
|
||||
|
||||
expect(columnList.registerColumn).toHaveBeenCalled();
|
||||
|
||||
let columns = documentList.data.getColumns();
|
||||
expect(columns.length).toBe(1);
|
||||
expect(columns[0]).toBe(column);
|
||||
});
|
||||
|
||||
it('should setup screen reader title for thumbnail column', () => {
|
||||
let column = new ContentColumnComponent(columnList, logService);
|
||||
column.key = '$thumbnail';
|
||||
column.ngOnInit();
|
||||
|
||||
expect(column.srTitle).toBe('Thumbnail');
|
||||
});
|
||||
|
||||
it('should register on init', () => {
|
||||
let column = new ContentColumnComponent(columnList, logService);
|
||||
spyOn(column, 'register').and.callThrough();
|
||||
|
||||
column.ngAfterContentInit();
|
||||
expect(column.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require action list to register action with', () => {
|
||||
let column = new ContentColumnComponent(columnList, logService);
|
||||
expect(column.register()).toBeTruthy();
|
||||
|
||||
column = new ContentColumnComponent(null, logService);
|
||||
expect(column.register()).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { DataColumn } from '@alfresco/core';
|
||||
import { LogService } from '@alfresco/core';
|
||||
import { AfterContentInit, Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
|
||||
|
||||
import { ContentColumnListComponent } from './content-column-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'content-column',
|
||||
template: ''
|
||||
})
|
||||
export class ContentColumnComponent implements OnInit, AfterContentInit, DataColumn {
|
||||
|
||||
@Input()
|
||||
key: string;
|
||||
|
||||
@Input()
|
||||
type: string = 'text';
|
||||
|
||||
@Input()
|
||||
format: string;
|
||||
|
||||
@Input()
|
||||
sortable: boolean = false;
|
||||
|
||||
@Input()
|
||||
title: string = '';
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
/**
|
||||
* Title to be used for screen readers.
|
||||
*/
|
||||
@Input('sr-title')
|
||||
srTitle: string;
|
||||
|
||||
@Input('class')
|
||||
cssClass: string;
|
||||
|
||||
constructor(private list: ContentColumnListComponent, private logService: LogService) {
|
||||
this.logService.log('ContentColumnComponent is deprecated starting with 1.7.0 and may be removed in future versions. Use DataColumnComponent instead.');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.srTitle && this.key === '$thumbnail') {
|
||||
this.srTitle = 'Thumbnail';
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.register();
|
||||
}
|
||||
|
||||
register(): boolean {
|
||||
if (this.list) {
|
||||
return this.list.registerColumn(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<adf-datatable
|
||||
[selectionMode]="selectionMode"
|
||||
[data]="data"
|
||||
[actions]="contentActions"
|
||||
[actionsPosition]="contentActionsPosition"
|
||||
[multiselect]="multiselect"
|
||||
[allowDropFiles]="allowDropFiles"
|
||||
[contextMenu]="contextMenuActions"
|
||||
[rowStyle]="rowStyle"
|
||||
[rowStyleClass]="rowStyleClass"
|
||||
[loading]="loading"
|
||||
[noPermission]="noPermission"
|
||||
[showHeader]="!isEmpty()"
|
||||
(showRowContextMenu)="onShowRowContextMenu($event)"
|
||||
(showRowActionsMenu)="onShowRowActionsMenu($event)"
|
||||
(executeRowAction)="onExecuteRowAction($event)"
|
||||
(rowClick)="onNodeClick($event.value?.node)"
|
||||
(rowDblClick)="onNodeDblClick($event.value?.node)"
|
||||
(row-select)="onNodeSelect($event.detail)"
|
||||
(row-unselect)="onNodeUnselect($event.detail)">
|
||||
|
||||
<div *ngIf="!isEmptyTemplateDefined()">
|
||||
<no-content-template>
|
||||
<ng-template>
|
||||
<adf-empty-list>
|
||||
<div class="adf-empty-list_template adf-empty-folder">
|
||||
<div class="adf-empty-folder-this-space-is-empty">{{'ADF-DOCUMENT-LIST.EMPTY.HEADER' | translate}}</div>
|
||||
<div fxHide.lt-md="true" class="adf-empty-folder-drag-drop">{{ 'ADF-DATATABLE.EMPTY.DRAG-AND-DROP.TITLE' | translate }}</div>
|
||||
<div fxHide.lt-md="true" class="adf-empty-folder-any-files-here-to-add">{{ 'ADF-DATATABLE.EMPTY.DRAG-AND-DROP.SUBTITLE' | translate }}</div>
|
||||
<img class="adf-empty-folder-image" [src]="emptyFolderImageUrl">
|
||||
</div>
|
||||
<!-- <div adf-empty-list-header class="adf-empty-list-header"> {{'ADF-DOCUMENT-LIST.EMPTY.HEADER' | translate}} </div> -->
|
||||
</adf-empty-list>
|
||||
</ng-template>
|
||||
</no-content-template>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isNoPermissionTemplateDefined()">
|
||||
<no-permission-template>
|
||||
<ng-template>
|
||||
<div class="adf-no-permission__template">
|
||||
<mat-icon>ic_error</mat-icon>
|
||||
<p class="adf-no-permission__template--text">{{ 'ADF-DOCUMENT-LIST.NO_PERMISSION' | translate }}</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</no-permission-template>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<loading-content-template>
|
||||
<ng-template>
|
||||
<div class="adf-document-list-loading-container">
|
||||
<mat-progress-spinner
|
||||
id="adf-document-list-loading"
|
||||
class="adf-document-list-loading-margin"
|
||||
[color]="'primary'"
|
||||
[mode]="'indeterminate'">
|
||||
</mat-progress-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
</loading-content-template>
|
||||
</div>
|
||||
</adf-datatable>
|
||||
|
||||
<ng-container *ngIf="isPaginationEnabled()">
|
||||
<adf-pagination
|
||||
*ngIf="isPaginationNeeded()"
|
||||
class="adf-documentlist-pagination"
|
||||
(changePageSize)="onChangePageSize($event)"
|
||||
(changePageNumber)="onChangePageNumber($event)"
|
||||
(nextPage)="onNextPage($event)"
|
||||
(prevPage)="onPrevPage($event)"
|
||||
[pagination]="pagination"
|
||||
[supportedPageSizes]="supportedPageSizes">
|
||||
</adf-pagination>
|
||||
|
||||
<adf-infinite-pagination
|
||||
*ngIf="!isPaginationNeeded()"
|
||||
[pagination]="pagination"
|
||||
[pageSize]="pageSize"
|
||||
[loading]="infiniteLoading"
|
||||
(loadMore)="loadNextBatch($event)">
|
||||
{{ 'ADF-DOCUMENT-LIST.LAYOUT.LOAD_MORE' | translate }}
|
||||
</adf-infinite-pagination>
|
||||
</ng-container>
|
@@ -0,0 +1,133 @@
|
||||
@mixin adf-document-list-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
adf-datatable > table > tbody > tr.is-selected > td.adf-data-table-cell.adf-data-table-cell--image.image-table-cell > div > div > mat-icon > svg {
|
||||
fill: mat-color($accent);
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.document-list_empty_template {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.document-list__this-space-is-empty {
|
||||
height: 32px;
|
||||
opacity: 0.26;
|
||||
font-size: 24px;
|
||||
line-height: 1.33;
|
||||
letter-spacing: -1px;
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.document-list__drag-drop {
|
||||
height: 56px;
|
||||
opacity: 0.54;
|
||||
font-size: 56px;
|
||||
line-height: 1;
|
||||
letter-spacing: -2px;
|
||||
color: mat-color($foreground, text);
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.document-list__any-files-here-to-add {
|
||||
height: 24px;
|
||||
opacity: 0.54;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.4px;
|
||||
color: mat-color($foreground, text)0;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.document-list__empty_doc_lib {
|
||||
width: 565px;
|
||||
height: 161px;
|
||||
object-fit: contain;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.adf-document-list-loading-margin {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.adf-document-list-loading-container {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.adf-empty-list-header {
|
||||
height: 32px;
|
||||
opacity: 0.26 !important;
|
||||
font-size: 24px;
|
||||
line-height: 1.33;
|
||||
letter-spacing: -1px;
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.adf-documentlist-pagination {
|
||||
color: mat-color($foreground, text);
|
||||
|
||||
.adf-pagination__block {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-empty-folder {
|
||||
|
||||
&-this-space-is-empty {
|
||||
height: 32px;
|
||||
opacity: 0.26;
|
||||
font-size: 24px;
|
||||
line-height: 1.33;
|
||||
letter-spacing: -1px;
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
&-drag-drop {
|
||||
min-height: 56px;
|
||||
opacity: 0.54;
|
||||
font-size: 53px;
|
||||
line-height: 1;
|
||||
letter-spacing: -2px;
|
||||
color: mat-color($foreground, text);
|
||||
margin-top: 40px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
|
||||
@media screen and ($mat-xsmall) {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
&-any-files-here-to-add {
|
||||
min-height: 24px;
|
||||
opacity: 0.54;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.4px;
|
||||
color: mat-color($foreground, text);
|
||||
margin-top: 17px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
&-image {
|
||||
width: 565px;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
margin-top: 17px;
|
||||
|
||||
@media screen and ($mat-xsmall) {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,894 @@
|
||||
/*!
|
||||
* @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 {
|
||||
DataCellEvent,
|
||||
DataColumn,
|
||||
DataRowActionEvent,
|
||||
DataSorting,
|
||||
DataTableComponent,
|
||||
ObjectDataColumn
|
||||
} from '@alfresco/core';
|
||||
import { AlfrescoApiService, AppConfigService, DataColumnListComponent, UserPreferencesService } from '@alfresco/core';
|
||||
import {
|
||||
AfterContentInit, Component, ContentChild, ElementRef, EventEmitter, HostListener, Input, NgZone,
|
||||
OnChanges, OnInit, Output, SimpleChanges, TemplateRef, ViewChild, ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {
|
||||
DeletedNodesPaging,
|
||||
MinimalNodeEntity,
|
||||
MinimalNodeEntryEntity,
|
||||
NodePaging,
|
||||
Pagination,
|
||||
PersonEntry,
|
||||
SitePaging
|
||||
} from 'alfresco-js-api';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
import { presetsDefaultModel } from '../models/preset.model';
|
||||
import { ImageResolver } from './../data/image-resolver.model';
|
||||
import { RowFilter } from './../data/row-filter.model';
|
||||
import { ShareDataRow } from './../data/share-data-row.model';
|
||||
import { ShareDataTableAdapter } from './../data/share-datatable-adapter';
|
||||
|
||||
import { ContentActionModel } from './../models/content-action.model';
|
||||
import { PermissionStyleModel } from './../models/permissions-style.model';
|
||||
import { DocumentListService } from './../services/document-list.service';
|
||||
import { NodeEntityEvent, NodeEntryEvent } from './node.event';
|
||||
|
||||
declare var require: any;
|
||||
|
||||
export enum PaginationStrategy {
|
||||
Finite,
|
||||
Infinite
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-document-list',
|
||||
styleUrls: ['./document-list.component.scss'],
|
||||
templateUrl: './document-list.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DocumentListComponent implements OnInit, OnChanges, AfterContentInit {
|
||||
|
||||
static SINGLE_CLICK_NAVIGATION: string = 'click';
|
||||
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
|
||||
static DEFAULT_PAGE_SIZE: number = 20;
|
||||
|
||||
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
|
||||
|
||||
@Input()
|
||||
permissionsStyle: PermissionStyleModel[] = [];
|
||||
|
||||
@Input()
|
||||
locationFormat: string = '/';
|
||||
|
||||
@Input()
|
||||
navigate: boolean = true;
|
||||
|
||||
@Input()
|
||||
navigationMode: string = DocumentListComponent.DOUBLE_CLICK_NAVIGATION; // click|dblclick
|
||||
|
||||
@Input()
|
||||
thumbnails: boolean = false;
|
||||
|
||||
@Input()
|
||||
selectionMode: string = 'single'; // null|single|multiple
|
||||
|
||||
@Input()
|
||||
multiselect: boolean = false;
|
||||
|
||||
@Input()
|
||||
enablePagination: boolean = true;
|
||||
|
||||
@Input()
|
||||
contentActions: boolean = false;
|
||||
|
||||
@Input()
|
||||
contentActionsPosition: string = 'right'; // left|right
|
||||
|
||||
@Input()
|
||||
contextMenuActions: boolean = false;
|
||||
|
||||
@Input()
|
||||
pageSize: number = DocumentListComponent.DEFAULT_PAGE_SIZE;
|
||||
|
||||
@Input()
|
||||
emptyFolderImageUrl: string = require('../../assets/images/empty_doc_lib.svg');
|
||||
|
||||
@Input()
|
||||
allowDropFiles: boolean = false;
|
||||
|
||||
@Input()
|
||||
sorting: string[];
|
||||
|
||||
@Input()
|
||||
rowStyle: string;
|
||||
|
||||
@Input()
|
||||
rowStyleClass: string;
|
||||
|
||||
@Input()
|
||||
loading: boolean = false;
|
||||
|
||||
@Input()
|
||||
paginationStrategy: PaginationStrategy = PaginationStrategy.Finite;
|
||||
|
||||
@Input()
|
||||
supportedPageSizes: number[];
|
||||
|
||||
infiniteLoading: boolean = false;
|
||||
noPermission: boolean = false;
|
||||
|
||||
selection = new Array<MinimalNodeEntity>();
|
||||
skipCount: number = 0;
|
||||
pagination: Pagination;
|
||||
|
||||
@Input()
|
||||
rowFilter: RowFilter | null = null;
|
||||
|
||||
@Input()
|
||||
imageResolver: ImageResolver | null = null;
|
||||
|
||||
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||
@Input()
|
||||
currentFolderId: string = null;
|
||||
|
||||
@Input()
|
||||
folderNode: MinimalNodeEntryEntity = null;
|
||||
|
||||
@Input()
|
||||
node: NodePaging = null;
|
||||
|
||||
@Output()
|
||||
nodeClick: EventEmitter<NodeEntityEvent> = new EventEmitter<NodeEntityEvent>();
|
||||
|
||||
@Output()
|
||||
nodeDblClick: EventEmitter<NodeEntityEvent> = new EventEmitter<NodeEntityEvent>();
|
||||
|
||||
@Output()
|
||||
folderChange: EventEmitter<NodeEntryEvent> = new EventEmitter<NodeEntryEvent>();
|
||||
|
||||
@Output()
|
||||
preview: EventEmitter<NodeEntityEvent> = new EventEmitter<NodeEntityEvent>();
|
||||
|
||||
@Output()
|
||||
ready: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@ViewChild(DataTableComponent)
|
||||
dataTable: DataTableComponent;
|
||||
|
||||
errorMessage;
|
||||
actions: ContentActionModel[] = [];
|
||||
emptyFolderTemplate: TemplateRef<any>;
|
||||
noPermissionTemplate: TemplateRef<any>;
|
||||
contextActionHandler: Subject<any> = new Subject();
|
||||
data: ShareDataTableAdapter;
|
||||
|
||||
private layoutPresets = {};
|
||||
private currentNodeAllowableOperations: string[] = [];
|
||||
private CREATE_PERMISSION = 'create';
|
||||
private defaultPageSizes = [5, 10, 15, 20];
|
||||
|
||||
constructor(private documentListService: DocumentListService,
|
||||
private ngZone: NgZone,
|
||||
private elementRef: ElementRef,
|
||||
private apiService: AlfrescoApiService,
|
||||
private appConfig: AppConfigService,
|
||||
private preferences: UserPreferencesService) {
|
||||
this.supportedPageSizes = appConfig.get('document-list.supportedPageSizes', this.defaultPageSizes);
|
||||
}
|
||||
|
||||
getContextActions(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
let actions = this.getNodeActions(node);
|
||||
if (actions && actions.length > 0) {
|
||||
return actions.map((currentAction: ContentActionModel) => {
|
||||
return {
|
||||
model: currentAction,
|
||||
node: node,
|
||||
subject: this.contextActionHandler
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
contextActionCallback(action) {
|
||||
if (action) {
|
||||
this.executeContentAction(action.node, action.model);
|
||||
}
|
||||
}
|
||||
|
||||
get hasCustomLayout(): boolean {
|
||||
return this.columnList && this.columnList.columns && this.columnList.columns.length > 0;
|
||||
}
|
||||
|
||||
getDefaultPageSize(): number {
|
||||
let result = this.preferences.paginationSize;
|
||||
|
||||
const pageSizes = this.supportedPageSizes || this.defaultPageSizes;
|
||||
if (pageSizes && pageSizes.length > 0 && pageSizes.indexOf(result) < 0) {
|
||||
result = pageSizes[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.pageSize = this.getDefaultPageSize();
|
||||
this.loadLayoutPresets();
|
||||
this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting());
|
||||
this.data.thumbnails = this.thumbnails;
|
||||
this.data.permissionsStyle = this.permissionsStyle;
|
||||
|
||||
if (this.rowFilter) {
|
||||
this.data.setFilter(this.rowFilter);
|
||||
}
|
||||
|
||||
if (this.imageResolver) {
|
||||
this.data.setImageResolver(this.imageResolver);
|
||||
}
|
||||
|
||||
this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
|
||||
|
||||
this.enforceSingleClickNavigationForMobile();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
let schema: DataColumn[] = [];
|
||||
|
||||
if (this.hasCustomLayout) {
|
||||
schema = this.columnList.columns.map(c => <DataColumn> c);
|
||||
}
|
||||
|
||||
if (!this.data) {
|
||||
this.data = new ShareDataTableAdapter(this.documentListService, schema, this.getDefaultSorting());
|
||||
} else if (schema && schema.length > 0) {
|
||||
this.data.setColumns(schema);
|
||||
}
|
||||
|
||||
let columns = this.data.getColumns();
|
||||
if (!columns || columns.length === 0) {
|
||||
this.setupDefaultColumns(this.currentFolderId);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.folderNode && changes.folderNode.currentValue) {
|
||||
this.loadFolder();
|
||||
} else if (changes.currentFolderId && changes.currentFolderId.currentValue) {
|
||||
if (changes.currentFolderId.previousValue !== changes.currentFolderId.currentValue) {
|
||||
this.resetPagination();
|
||||
this.folderNode = null;
|
||||
}
|
||||
if (!this.hasCustomLayout) {
|
||||
this.setupDefaultColumns(changes.currentFolderId.currentValue);
|
||||
}
|
||||
this.loadFolderByNodeId(changes.currentFolderId.currentValue);
|
||||
} else if (this.data) {
|
||||
if (changes.node && changes.node.currentValue) {
|
||||
this.resetSelection();
|
||||
this.data.loadPage(changes.node.currentValue);
|
||||
} else if (changes.rowFilter) {
|
||||
this.data.setFilter(changes.rowFilter.currentValue);
|
||||
if (this.currentFolderId) {
|
||||
this.loadFolderNodesByFolderNodeId(this.currentFolderId, this.pageSize, this.skipCount);
|
||||
}
|
||||
} else if (changes.imageResolver) {
|
||||
this.data.setImageResolver(changes.imageResolver.currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reload(merge: boolean = false) {
|
||||
this.ngZone.run(() => {
|
||||
this.resetSelection();
|
||||
|
||||
if (this.folderNode) {
|
||||
this.loadFolder(merge);
|
||||
} else if (this.currentFolderId) {
|
||||
this.loadFolderByNodeId(this.currentFolderId);
|
||||
} else if (this.node) {
|
||||
this.data.loadPage(this.node);
|
||||
this.ready.emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isEmptyTemplateDefined(): boolean {
|
||||
if (this.dataTable) {
|
||||
if (this.emptyFolderTemplate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isNoPermissionTemplateDefined(): boolean {
|
||||
if (this.dataTable) {
|
||||
if (this.noPermissionTemplate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isMobile(): boolean {
|
||||
return !!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return !this.data || this.data.getRows().length === 0;
|
||||
}
|
||||
|
||||
isPaginationEnabled() {
|
||||
return this.enablePagination && !this.isEmpty();
|
||||
}
|
||||
|
||||
isPaginationNeeded() {
|
||||
return this.paginationStrategy === PaginationStrategy.Finite;
|
||||
}
|
||||
|
||||
getNodeActions(node: MinimalNodeEntity | any): ContentActionModel[] {
|
||||
let target = null;
|
||||
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFile) {
|
||||
target = 'document';
|
||||
}
|
||||
|
||||
if (node.entry.isFolder) {
|
||||
target = 'folder';
|
||||
}
|
||||
|
||||
if (target) {
|
||||
let ltarget = target.toLowerCase();
|
||||
let actionsByTarget = this.actions.filter(entry => {
|
||||
return entry.target.toLowerCase() === ltarget;
|
||||
}).map(action => new ContentActionModel(action));
|
||||
|
||||
actionsByTarget.forEach((action) => {
|
||||
this.checkPermission(node, action);
|
||||
});
|
||||
|
||||
return actionsByTarget;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
checkPermission(node: any, action: ContentActionModel): ContentActionModel {
|
||||
if (action.permission) {
|
||||
if (this.hasPermissions(node)) {
|
||||
let permissions = node.entry.allowableOperations;
|
||||
let findPermission = permissions.find(permission => permission === action.permission);
|
||||
if (!findPermission && action.disableWithNoPermission === true) {
|
||||
action.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private hasPermissions(node: any): boolean {
|
||||
return node.entry.allowableOperations ? true : false;
|
||||
}
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
onShowContextMenu(e?: Event) {
|
||||
if (e && this.contextMenuActions) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
performNavigation(node: MinimalNodeEntity): boolean {
|
||||
if (this.canNavigateFolder(node)) {
|
||||
this.currentFolderId = node.entry.id;
|
||||
this.folderNode = node.entry;
|
||||
this.skipCount = 0;
|
||||
this.currentNodeAllowableOperations = node.entry['allowableOperations'] ? node.entry['allowableOperations'] : [];
|
||||
this.loadFolder();
|
||||
this.folderChange.emit(new NodeEntryEvent(node.entry));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when executing content action for a document or folder.
|
||||
* @param node Node to be the context of the execution.
|
||||
* @param action Action to be executed against the context.
|
||||
*/
|
||||
executeContentAction(node: MinimalNodeEntity, action: ContentActionModel) {
|
||||
if (node && node.entry && action) {
|
||||
let handlerSub;
|
||||
|
||||
if (typeof action.handler === 'function') {
|
||||
handlerSub = action.handler(node, this, action.permission);
|
||||
} else {
|
||||
handlerSub = Observable.of(true);
|
||||
}
|
||||
|
||||
if (typeof action.execute === 'function') {
|
||||
handlerSub.subscribe(() => {
|
||||
action.execute(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadFolder(merge: boolean = false) {
|
||||
if (merge) {
|
||||
this.infiniteLoading = true;
|
||||
} else {
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
let nodeId = this.folderNode ? this.folderNode.id : this.currentFolderId;
|
||||
if (nodeId) {
|
||||
this.loadFolderNodesByFolderNodeId(nodeId, this.pageSize, this.skipCount, merge).catch(err => this.error.emit(err));
|
||||
}
|
||||
}
|
||||
|
||||
// gets folder node and its content
|
||||
loadFolderByNodeId(nodeId: string) {
|
||||
this.loading = true;
|
||||
this.resetSelection();
|
||||
|
||||
if (nodeId === '-trashcan-') {
|
||||
this.loadTrashcan();
|
||||
} else if (nodeId === '-sharedlinks-') {
|
||||
this.loadSharedLinks();
|
||||
} else if (nodeId === '-sites-') {
|
||||
this.loadSites();
|
||||
} else if (nodeId === '-mysites-') {
|
||||
this.loadMemberSites();
|
||||
} else if (nodeId === '-favorites-') {
|
||||
this.loadFavorites();
|
||||
} else if (nodeId === '-recent-') {
|
||||
this.loadRecent();
|
||||
} else {
|
||||
this.documentListService
|
||||
.getFolderNode(nodeId)
|
||||
.then(node => {
|
||||
this.folderNode = node;
|
||||
this.currentFolderId = node.id;
|
||||
this.skipCount = 0;
|
||||
this.currentNodeAllowableOperations = node['allowableOperations'] ? node['allowableOperations'] : [];
|
||||
return this.loadFolderNodesByFolderNodeId(node.id, this.pageSize, this.skipCount);
|
||||
})
|
||||
.catch(err => {
|
||||
if (JSON.parse(err.message).error.statusCode === 403) {
|
||||
this.loading = false;
|
||||
this.noPermission = true;
|
||||
}
|
||||
this.error.emit(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadFolderNodesByFolderNodeId(id: string, maxItems: number, skipCount: number, merge: boolean = false): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resetSelection();
|
||||
this.documentListService
|
||||
.getFolder(null, {
|
||||
maxItems: maxItems,
|
||||
skipCount: skipCount,
|
||||
rootFolderId: id
|
||||
})
|
||||
.subscribe(
|
||||
val => {
|
||||
if (this.isCurrentPageEmpty(val, skipCount)) {
|
||||
this.updateSkipCount(skipCount - maxItems);
|
||||
this.loadFolderNodesByFolderNodeId(id, maxItems, skipCount - maxItems).then(
|
||||
() => resolve(true),
|
||||
error => reject(error)
|
||||
);
|
||||
} else {
|
||||
this.data.loadPage(<NodePaging> val, merge);
|
||||
this.pagination = val.list.pagination;
|
||||
this.loading = false;
|
||||
this.infiniteLoading = false;
|
||||
this.ready.emit();
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
resetSelection() {
|
||||
this.dataTable.resetSelection();
|
||||
this.selection = [];
|
||||
}
|
||||
|
||||
resetPagination() {
|
||||
this.skipCount = 0;
|
||||
}
|
||||
|
||||
private loadTrashcan(): void {
|
||||
const options = {
|
||||
include: ['path', 'properties'],
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount
|
||||
};
|
||||
this.apiService.nodesApi.getDeletedNodes(options)
|
||||
.then((page: DeletedNodesPaging) => this.onPageLoaded(page))
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private loadSharedLinks(): void {
|
||||
const options = {
|
||||
include: ['properties', 'allowableOperations', 'path'],
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount
|
||||
};
|
||||
this.apiService.sharedLinksApi.findSharedLinks(options)
|
||||
.then((page: NodePaging) => this.onPageLoaded(page))
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private loadSites(): void {
|
||||
const options = {
|
||||
include: ['properties'],
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount
|
||||
};
|
||||
|
||||
this.apiService.sitesApi.getSites(options)
|
||||
.then((page: NodePaging) => this.onPageLoaded(page))
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private loadMemberSites(): void {
|
||||
const options = {
|
||||
include: ['properties'],
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount
|
||||
};
|
||||
|
||||
this.apiService.peopleApi.getSiteMembership('-me-', options)
|
||||
.then((result: SitePaging) => {
|
||||
let page: NodePaging = {
|
||||
list: {
|
||||
entries: result.list.entries
|
||||
.map(({ entry: { site } }: any) => ({
|
||||
entry: site
|
||||
})),
|
||||
pagination: result.list.pagination
|
||||
}
|
||||
};
|
||||
|
||||
this.onPageLoaded(page);
|
||||
})
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private loadFavorites(): void {
|
||||
const options = {
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount,
|
||||
where: '(EXISTS(target/file) OR EXISTS(target/folder))',
|
||||
include: ['properties', 'allowableOperations', 'path']
|
||||
};
|
||||
|
||||
this.apiService.favoritesApi.getFavorites('-me-', options)
|
||||
.then((result: NodePaging) => {
|
||||
let page: NodePaging = {
|
||||
list: {
|
||||
entries: result.list.entries
|
||||
.map(({ entry: { target } }: any) => ({
|
||||
entry: target.file || target.folder
|
||||
}))
|
||||
.map(({ entry }: any) => {
|
||||
entry.properties = {
|
||||
'cm:title': entry.title,
|
||||
'cm:description': entry.description
|
||||
};
|
||||
return { entry };
|
||||
}),
|
||||
pagination: result.list.pagination
|
||||
}
|
||||
};
|
||||
this.onPageLoaded(page);
|
||||
})
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private loadRecent(): void {
|
||||
this.apiService.peopleApi.getPerson('-me-')
|
||||
.then((person: PersonEntry) => {
|
||||
const username = person.entry.id;
|
||||
const query = {
|
||||
query: {
|
||||
query: '*',
|
||||
language: 'afts'
|
||||
},
|
||||
filterQueries: [
|
||||
{ query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` },
|
||||
{ query: `cm:modifier:${username} OR cm:creator:${username}` },
|
||||
{ query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` }
|
||||
],
|
||||
include: ['path', 'properties', 'allowableOperations'],
|
||||
sort: [{
|
||||
type: 'FIELD',
|
||||
field: 'cm:modified',
|
||||
ascending: false
|
||||
}],
|
||||
paging: {
|
||||
maxItems: this.pageSize,
|
||||
skipCount: this.skipCount
|
||||
}
|
||||
};
|
||||
|
||||
return this.apiService.searchApi.search(query);
|
||||
})
|
||||
.then((page: NodePaging) => this.onPageLoaded(page))
|
||||
.catch(error => this.error.emit(error));
|
||||
}
|
||||
|
||||
private onPageLoaded(page: NodePaging) {
|
||||
if (page) {
|
||||
this.data.loadPage(page);
|
||||
this.pagination = page.list.pagination;
|
||||
this.loading = false;
|
||||
this.ready.emit();
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentPageEmpty(node, skipCount): boolean {
|
||||
return !this.hasNodeEntries(node) && this.hasPages(skipCount);
|
||||
}
|
||||
|
||||
private hasPages(skipCount): boolean {
|
||||
return skipCount > 0 && this.isPaginationEnabled();
|
||||
}
|
||||
|
||||
private hasNodeEntries(node): boolean {
|
||||
return node && node.list && node.list.entries && node.list.entries.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of predefined columns.
|
||||
*/
|
||||
setupDefaultColumns(preset: string = 'default'): void {
|
||||
if (this.data) {
|
||||
const columns = this.getLayoutPreset(preset);
|
||||
this.data.setColumns(columns);
|
||||
}
|
||||
}
|
||||
|
||||
onPreviewFile(node: MinimalNodeEntity) {
|
||||
if (node) {
|
||||
this.preview.emit(new NodeEntityEvent(node));
|
||||
}
|
||||
}
|
||||
|
||||
onNodeClick(node: MinimalNodeEntity) {
|
||||
const domEvent = new CustomEvent('node-click', {
|
||||
detail: {
|
||||
sender: this,
|
||||
node: node
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
|
||||
const event = new NodeEntityEvent(node);
|
||||
this.nodeClick.emit(event);
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
if (this.navigate && this.navigationMode === DocumentListComponent.SINGLE_CLICK_NAVIGATION) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFile) {
|
||||
this.onPreviewFile(node);
|
||||
}
|
||||
|
||||
if (node.entry.isFolder) {
|
||||
this.performNavigation(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNodeDblClick(node: MinimalNodeEntity) {
|
||||
const domEvent = new CustomEvent('node-dblclick', {
|
||||
detail: {
|
||||
sender: this,
|
||||
node: node
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
|
||||
const event = new NodeEntityEvent(node);
|
||||
this.nodeDblClick.emit(event);
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
if (this.navigate && this.navigationMode === DocumentListComponent.DOUBLE_CLICK_NAVIGATION) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFile) {
|
||||
this.onPreviewFile(node);
|
||||
}
|
||||
|
||||
if (node.entry.isFolder) {
|
||||
this.performNavigation(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
|
||||
this.selection = event.selection.map(entry => entry.node);
|
||||
const domEvent = new CustomEvent('node-select', {
|
||||
detail: {
|
||||
node: event.row.node,
|
||||
selection: this.selection
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
}
|
||||
|
||||
onNodeUnselect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
|
||||
this.selection = event.selection.map(entry => entry.node);
|
||||
const domEvent = new CustomEvent('node-unselect', {
|
||||
detail: {
|
||||
node: event.row.node,
|
||||
selection: this.selection
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
}
|
||||
|
||||
onShowRowContextMenu(event: DataCellEvent) {
|
||||
if (this.contextMenuActions) {
|
||||
let args = event.value;
|
||||
let node = (<ShareDataRow> args.row).node;
|
||||
if (node) {
|
||||
args.actions = this.getContextActions(node) || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onShowRowActionsMenu(event: DataCellEvent) {
|
||||
if (this.contentActions) {
|
||||
let args = event.value;
|
||||
let node = (<ShareDataRow> args.row).node;
|
||||
if (node) {
|
||||
args.actions = this.getNodeActions(node) || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExecuteRowAction(event: DataRowActionEvent) {
|
||||
if (this.contentActions) {
|
||||
let args = event.value;
|
||||
let node = (<ShareDataRow> args.row).node;
|
||||
let action = (<ContentActionModel> args.action);
|
||||
this.executeContentAction(node, action);
|
||||
}
|
||||
}
|
||||
|
||||
onChangePageSize(event: Pagination): void {
|
||||
this.preferences.paginationSize = event.maxItems;
|
||||
this.pageSize = event.maxItems;
|
||||
this.skipCount = 0;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
onChangePageNumber(page: Pagination): void {
|
||||
this.pageSize = page.maxItems;
|
||||
this.skipCount = page.skipCount;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
onNextPage(event: Pagination): void {
|
||||
this.skipCount = event.skipCount;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
loadNextBatch(event: Pagination) {
|
||||
this.skipCount = event.skipCount;
|
||||
this.reload(true);
|
||||
}
|
||||
|
||||
onPrevPage(event: Pagination): void {
|
||||
this.skipCount = event.skipCount;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
private enforceSingleClickNavigationForMobile(): void {
|
||||
if (this.isMobile()) {
|
||||
this.navigationMode = DocumentListComponent.SINGLE_CLICK_NAVIGATION;
|
||||
}
|
||||
}
|
||||
|
||||
private getDefaultSorting(): DataSorting {
|
||||
let defaultSorting: DataSorting;
|
||||
if (this.sorting) {
|
||||
const [key, direction] = this.sorting;
|
||||
defaultSorting = new DataSorting(key, direction);
|
||||
}
|
||||
return defaultSorting;
|
||||
}
|
||||
|
||||
canNavigateFolder(node: MinimalNodeEntity): boolean {
|
||||
if (this.isCustomSource(this.currentFolderId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node && node.entry && node.entry.isFolder) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isCustomSource(folderId: string): boolean {
|
||||
const sources = ['-trashcan-', '-sharedlinks-', '-sites-', '-mysites-', '-favorites-', '-recent-'];
|
||||
|
||||
if (sources.indexOf(folderId) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
updateSkipCount(newSkipCount) {
|
||||
this.skipCount = newSkipCount;
|
||||
}
|
||||
|
||||
hasCurrentNodePermission(permission: string): boolean {
|
||||
let hasPermission: boolean = false;
|
||||
if (this.currentNodeAllowableOperations.length > 0) {
|
||||
let permFound = this.currentNodeAllowableOperations.find(element => element === permission);
|
||||
hasPermission = permFound ? true : false;
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
hasCreatePermission() {
|
||||
return this.hasCurrentNodePermission(this.CREATE_PERMISSION);
|
||||
}
|
||||
|
||||
private loadLayoutPresets(): void {
|
||||
const externalSettings = this.appConfig.get('document-list.presets', null);
|
||||
|
||||
if (externalSettings) {
|
||||
this.layoutPresets = Object.assign({}, presetsDefaultModel, externalSettings);
|
||||
} else {
|
||||
this.layoutPresets = presetsDefaultModel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private getLayoutPreset(name: string = 'default'): DataColumn[] {
|
||||
return (this.layoutPresets[name] || this.layoutPresets['default']).map(col => new ObjectDataColumn(col));
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { DataTableComponent, DataTableModule } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { EmptyFolderContentDirective } from './empty-folder-content.directive';
|
||||
|
||||
describe('EmptyFolderContent', () => {
|
||||
|
||||
let emptyFolderContent: EmptyFolderContentDirective;
|
||||
let documentList: DocumentListComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
providers: [
|
||||
DocumentListService
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
documentList.dataTable = new DataTableComponent(null, null);
|
||||
emptyFolderContent = new EmptyFolderContentDirective(documentList);
|
||||
});
|
||||
|
||||
it('is defined', () => {
|
||||
expect(emptyFolderContent).toBeDefined();
|
||||
});
|
||||
|
||||
it('set template', () => {
|
||||
emptyFolderContent.template = '<example>';
|
||||
|
||||
emptyFolderContent.ngAfterContentInit();
|
||||
|
||||
expect(emptyFolderContent.template).toBe(documentList.emptyFolderTemplate);
|
||||
expect(emptyFolderContent.template).toBe(documentList.dataTable.noContentTemplate);
|
||||
});
|
||||
});
|
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* @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 { AfterContentInit, ContentChild, Directive, TemplateRef } from '@angular/core';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
|
||||
@Directive({
|
||||
selector: 'empty-folder-content'
|
||||
})
|
||||
export class EmptyFolderContentDirective implements AfterContentInit {
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
constructor(private documentList: DocumentListComponent) {
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.documentList.emptyFolderTemplate = this.template;
|
||||
this.documentList.dataTable.noContentTemplate = this.template;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* @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 { async, TestBed } from '@angular/core/testing';
|
||||
import { MatProgressSpinnerModule } from '@angular/material';
|
||||
import { DataTableComponent, DataTableModule } from '@alfresco/core';
|
||||
import { DocumentListService } from '../../services/document-list.service';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
import { NoPermissionContentDirective } from './no-permission-content.directive';
|
||||
|
||||
describe('NoPermissionContentDirective', () => {
|
||||
|
||||
let noPermissionContent: NoPermissionContentDirective;
|
||||
let documentList: DocumentListComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule,
|
||||
DataTableModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent
|
||||
],
|
||||
providers: [
|
||||
DocumentListService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
|
||||
documentList.dataTable = new DataTableComponent(null, null);
|
||||
noPermissionContent = new NoPermissionContentDirective(documentList);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(noPermissionContent).toBeDefined();
|
||||
});
|
||||
|
||||
it('should set template', () => {
|
||||
noPermissionContent.template = '<example>';
|
||||
|
||||
noPermissionContent.ngAfterContentInit();
|
||||
|
||||
expect(noPermissionContent.template).toBe(documentList.noPermissionTemplate);
|
||||
expect(noPermissionContent.template).toBe(documentList.dataTable.noPermissionTemplate);
|
||||
});
|
||||
});
|
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* @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 { AfterContentInit, ContentChild, Directive, TemplateRef } from '@angular/core';
|
||||
import { DocumentListComponent } from './../document-list.component';
|
||||
|
||||
@Directive({
|
||||
selector: 'no-permission-content'
|
||||
})
|
||||
export class NoPermissionContentDirective implements AfterContentInit {
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
constructor(private documentList: DocumentListComponent) {
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.documentList.noPermissionTemplate = this.template;
|
||||
this.documentList.dataTable.noPermissionTemplate = this.template;
|
||||
}
|
||||
}
|
33
lib/content-services/document-list/components/node.event.ts
Normal file
33
lib/content-services/document-list/components/node.event.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*!
|
||||
* @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 { BaseEvent } from '@alfresco/core';
|
||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
export class NodeEntityEvent extends BaseEvent<MinimalNodeEntity> {
|
||||
constructor(entity: MinimalNodeEntity) {
|
||||
super();
|
||||
this.value = entity;
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeEntryEvent extends BaseEvent<MinimalNodeEntryEntity> {
|
||||
constructor(entity: MinimalNodeEntryEntity) {
|
||||
super();
|
||||
this.value = entity;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* @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 { DataColumn, DataRow } from '@alfresco/core';
|
||||
|
||||
export type ImageResolver = (row: DataRow, column: DataColumn) => string;
|
20
lib/content-services/document-list/data/row-filter.model.ts
Normal file
20
lib/content-services/document-list/data/row-filter.model.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* @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 { ShareDataRow } from './share-data-row.model';
|
||||
|
||||
export type RowFilter = (value: ShareDataRow, index: number, array: ShareDataRow[]) => any;
|
@@ -0,0 +1,97 @@
|
||||
/*!
|
||||
* @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 { DataRow } from '@alfresco/core';
|
||||
import { ObjectUtils } from '@alfresco/core';
|
||||
import { MinimalNode, MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { PermissionStyleModel } from './../models/permissions-style.model';
|
||||
import { DocumentListService } from './../services/document-list.service';
|
||||
|
||||
export class ShareDataRow implements DataRow {
|
||||
|
||||
static ERR_OBJECT_NOT_FOUND: string = 'Object source not found';
|
||||
|
||||
cache: { [key: string]: any } = {};
|
||||
isSelected: boolean = false;
|
||||
isDropTarget: boolean;
|
||||
cssClass: string = '';
|
||||
|
||||
get node(): MinimalNodeEntity {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
constructor(private obj: MinimalNodeEntity, private documentListService: DocumentListService, private permissionsStyle: PermissionStyleModel[]) {
|
||||
if (!obj) {
|
||||
throw new Error(ShareDataRow.ERR_OBJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
this.isDropTarget = this.isFolderAndHasPermissionToUpload(obj);
|
||||
|
||||
if (permissionsStyle) {
|
||||
this.cssClass = this.getPermissionClass(obj);
|
||||
}
|
||||
}
|
||||
|
||||
getPermissionClass(nodeEntity: MinimalNodeEntity): string {
|
||||
let permissionsClasses = '';
|
||||
|
||||
this.permissionsStyle.forEach((currentPermissionsStyle: PermissionStyleModel) => {
|
||||
|
||||
if (this.applyPermissionStyleToFolder(nodeEntity.entry, currentPermissionsStyle) || this.applyPermissionStyleToFile(nodeEntity.entry, currentPermissionsStyle)) {
|
||||
|
||||
if (this.documentListService.hasPermission(nodeEntity.entry, currentPermissionsStyle.permission)) {
|
||||
permissionsClasses += ` ${currentPermissionsStyle.css}`;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return permissionsClasses;
|
||||
}
|
||||
|
||||
private applyPermissionStyleToFile(node: MinimalNode, currentPermissionsStyle: PermissionStyleModel): boolean {
|
||||
return (currentPermissionsStyle.isFile && node.isFile);
|
||||
}
|
||||
|
||||
private applyPermissionStyleToFolder(node: MinimalNode, currentPermissionsStyle: PermissionStyleModel): boolean {
|
||||
return (currentPermissionsStyle.isFolder && node.isFolder);
|
||||
}
|
||||
|
||||
isFolderAndHasPermissionToUpload(obj: MinimalNodeEntity): boolean {
|
||||
return this.isFolder(obj) && this.documentListService.hasPermission(obj.entry, 'create');
|
||||
}
|
||||
|
||||
isFolder(obj: MinimalNodeEntity): boolean {
|
||||
return obj.entry && obj.entry.isFolder;
|
||||
}
|
||||
|
||||
cacheValue(key: string, value: any): any {
|
||||
this.cache[key] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
getValue(key: string): any {
|
||||
if (this.cache[key] !== undefined) {
|
||||
return this.cache[key];
|
||||
}
|
||||
return ObjectUtils.getValue(this.obj.entry, key);
|
||||
}
|
||||
|
||||
hasValue(key: string): boolean {
|
||||
return this.getValue(key) !== undefined;
|
||||
}
|
||||
}
|
@@ -0,0 +1,485 @@
|
||||
/*!
|
||||
* @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 { async, TestBed } from '@angular/core/testing';
|
||||
import { DataColumn, DataRow, DataSorting } from '@alfresco/core';
|
||||
import { FileNode, FolderNode } from './../../mock';
|
||||
import { DocumentListService } from './../services/document-list.service';
|
||||
import { ShareDataRow } from './share-data-row.model';
|
||||
import { ShareDataTableAdapter } from './share-datatable-adapter';
|
||||
|
||||
describe('ShareDataTableAdapter', () => {
|
||||
|
||||
let documentListService: DocumentListService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DocumentListService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentListService = TestBed.get(DocumentListService);
|
||||
});
|
||||
|
||||
it('should setup rows and columns with constructor', () => {
|
||||
let schema = [<DataColumn> {}];
|
||||
let adapter = new ShareDataTableAdapter(documentListService, schema);
|
||||
|
||||
expect(adapter.getRows()).toEqual([]);
|
||||
expect(adapter.getColumns()).toEqual(schema);
|
||||
});
|
||||
|
||||
it('should setup columns when constructor is missing schema', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
expect(adapter.getColumns()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should set new columns', () => {
|
||||
let columns = [<DataColumn> {}, <DataColumn> {}];
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
adapter.setColumns(columns);
|
||||
expect(adapter.getColumns()).toEqual(columns);
|
||||
});
|
||||
|
||||
it('should reset columns', () => {
|
||||
let columns = [<DataColumn> {}, <DataColumn> {}];
|
||||
let adapter = new ShareDataTableAdapter(documentListService, columns);
|
||||
|
||||
expect(adapter.getColumns()).toEqual(columns);
|
||||
adapter.setColumns(null);
|
||||
expect(adapter.getColumns()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should set new rows', () => {
|
||||
let rows = [<DataRow> {}, <DataRow> {}];
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
expect(adapter.getRows()).toEqual([]);
|
||||
adapter.setRows(rows);
|
||||
expect(adapter.getRows()).toEqual(rows);
|
||||
});
|
||||
|
||||
it('should reset rows', () => {
|
||||
let rows = [<DataRow> {}, <DataRow> {}];
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
adapter.setRows(rows);
|
||||
expect(adapter.getRows()).toEqual(rows);
|
||||
|
||||
adapter.setRows(null);
|
||||
expect(adapter.getRows()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should sort new rows', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
spyOn(adapter, 'sort').and.callThrough();
|
||||
|
||||
let rows = [<DataRow> {}];
|
||||
adapter.setRows(rows);
|
||||
|
||||
expect(adapter.sort).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fail when getting value for missing row', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
let check = () => {
|
||||
return adapter.getValue(null, <DataColumn> {});
|
||||
};
|
||||
expect(check).toThrowError(adapter.ERR_ROW_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should fail when getting value for missing column', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
let check = () => {
|
||||
return adapter.getValue(<DataRow> {}, null);
|
||||
};
|
||||
expect(check).toThrowError(adapter.ERR_COL_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should covert cell value to formatted date', () => {
|
||||
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
|
||||
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.createdAt = rawValue;
|
||||
|
||||
let col = <DataColumn> {
|
||||
key: 'createdAt',
|
||||
type: 'date',
|
||||
format: 'medium' // Jul 15, 2015, 9:43:11 PM
|
||||
};
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe(dateValue);
|
||||
});
|
||||
|
||||
it('should use default date format as fallback', () => {
|
||||
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
|
||||
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.createdAt = rawValue;
|
||||
|
||||
let col = <DataColumn> {
|
||||
key: 'createdAt',
|
||||
type: 'date',
|
||||
format: null
|
||||
};
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe(dateValue);
|
||||
});
|
||||
|
||||
it('should return date value as string', () => {
|
||||
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.createdAt = rawValue;
|
||||
|
||||
let col = <DataColumn> {
|
||||
key: 'createdAt',
|
||||
type: 'string'
|
||||
};
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe(rawValue);
|
||||
});
|
||||
|
||||
it('should log error when having date conversion issues', () => {
|
||||
let dateValue = <Date> {};
|
||||
let file = new FileNode();
|
||||
file.entry.createdAt = <any> dateValue;
|
||||
|
||||
let col = <DataColumn> {
|
||||
key: 'createdAt',
|
||||
type: 'date',
|
||||
format: 'medium'
|
||||
};
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
spyOn(console, 'error').and.stub();
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe('Error');
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should generate fallback icon for a file thumbnail with missing mime type', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.content.mimeType = null;
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toContain(`assets/images/ft_ic_miscellaneous`);
|
||||
expect(value).toContain(`svg`);
|
||||
});
|
||||
|
||||
it('should generate fallback icon for a file with no content entry', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.content = null;
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toContain(`assets/images/ft_ic_miscellaneous`);
|
||||
expect(value).toContain(`svg`);
|
||||
});
|
||||
|
||||
it('should return image value unmodified', () => {
|
||||
let imageUrl = 'http://<address>';
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry['icon'] = imageUrl;
|
||||
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: 'icon'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe(imageUrl);
|
||||
});
|
||||
|
||||
it('should resolve folder icon', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let row = new ShareDataRow(new FolderNode(), documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toContain(`assets/images/ft_ic_folder`);
|
||||
expect(value).toContain(`svg`);
|
||||
});
|
||||
|
||||
it('should resolve file thumbnail', () => {
|
||||
let imageUrl: string = 'http://<addresss>';
|
||||
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl);
|
||||
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
adapter.thumbnails = true;
|
||||
|
||||
let file = new FileNode();
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toBe(imageUrl);
|
||||
expect(documentListService.getDocumentThumbnailUrl).toHaveBeenCalledWith(file);
|
||||
});
|
||||
|
||||
it('should resolve fallback file icon for unknown node', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.isFile = false;
|
||||
file.entry.isFolder = false;
|
||||
file.entry.content = null;
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toContain(`assets/images/ft_ic_miscellaneous`);
|
||||
expect(value).toContain(`svg`);
|
||||
});
|
||||
|
||||
it('should resolve file icon for content type', () => {
|
||||
let adapter = new ShareDataTableAdapter(documentListService, null);
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.isFile = false;
|
||||
file.entry.isFolder = false;
|
||||
file.entry.content.mimeType = 'image/png';
|
||||
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
let col = <DataColumn> {type: 'image', key: '$thumbnail'};
|
||||
|
||||
let value = adapter.getValue(row, col);
|
||||
expect(value).toContain(`assets/images/ft_ic_raster_image`);
|
||||
expect(value).toContain(`svg`);
|
||||
});
|
||||
|
||||
it('should put folders on top upon sort', () => {
|
||||
let file1 = new FileNode('file1');
|
||||
let file2 = new FileNode('file2');
|
||||
let folder = new FolderNode();
|
||||
|
||||
let col = <DataColumn> {key: 'name'};
|
||||
let adapter = new ShareDataTableAdapter(documentListService, [col]);
|
||||
adapter.setSorting(new DataSorting('name', 'asc'));
|
||||
|
||||
adapter.setRows([
|
||||
new ShareDataRow(file2, documentListService, null),
|
||||
new ShareDataRow(file1, documentListService, null),
|
||||
new ShareDataRow(folder, documentListService, null)
|
||||
]);
|
||||
|
||||
let sorted = adapter.getRows();
|
||||
expect((<ShareDataRow> sorted[0]).node).toBe(folder);
|
||||
expect((<ShareDataRow> sorted[1]).node).toBe(file1);
|
||||
expect((<ShareDataRow> sorted[2]).node).toBe(file2);
|
||||
});
|
||||
|
||||
it('should sort by dates up to ms', () => {
|
||||
let file1 = new FileNode('file1');
|
||||
file1.entry['dateProp'] = new Date(2016, 6, 30, 13, 14, 1);
|
||||
|
||||
let file2 = new FileNode('file2');
|
||||
file2.entry['dateProp'] = new Date(2016, 6, 30, 13, 14, 2);
|
||||
|
||||
let col = <DataColumn> {key: 'dateProp'};
|
||||
let adapter = new ShareDataTableAdapter(documentListService, [col]);
|
||||
|
||||
adapter.setRows([
|
||||
new ShareDataRow(file2, documentListService, null),
|
||||
new ShareDataRow(file1, documentListService, null)
|
||||
]);
|
||||
|
||||
adapter.sort('dateProp', 'asc');
|
||||
|
||||
let rows = adapter.getRows();
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file1);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file2);
|
||||
|
||||
adapter.sort('dateProp', 'desc');
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file2);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file1);
|
||||
});
|
||||
|
||||
it('should sort by file size', () => {
|
||||
let file1 = new FileNode('file1');
|
||||
let file2 = new FileNode('file2');
|
||||
let file3 = new FileNode('file3');
|
||||
let file4 = new FileNode('file4');
|
||||
|
||||
file1.entry.content.sizeInBytes = 146; // 146 bytes
|
||||
file2.entry.content.sizeInBytes = 10075; // 9.84 KB
|
||||
file3.entry.content.sizeInBytes = 4224120; // 4.03 MB
|
||||
file4.entry.content.sizeInBytes = 2852791665; // 2.66 GB
|
||||
|
||||
let col = <DataColumn> {key: 'content.sizeInBytes'};
|
||||
let adapter = new ShareDataTableAdapter(documentListService, [col]);
|
||||
|
||||
adapter.setRows([
|
||||
new ShareDataRow(file3, documentListService, null),
|
||||
new ShareDataRow(file4, documentListService, null),
|
||||
new ShareDataRow(file1, documentListService, null),
|
||||
new ShareDataRow(file2, documentListService, null)
|
||||
]);
|
||||
|
||||
adapter.sort('content.sizeInBytes', 'asc');
|
||||
let rows = adapter.getRows();
|
||||
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file1);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file2);
|
||||
expect((<ShareDataRow> rows[2]).node).toBe(file3);
|
||||
expect((<ShareDataRow> rows[3]).node).toBe(file4);
|
||||
|
||||
adapter.sort('content.sizeInBytes', 'desc');
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file4);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file3);
|
||||
expect((<ShareDataRow> rows[2]).node).toBe(file2);
|
||||
expect((<ShareDataRow> rows[3]).node).toBe(file1);
|
||||
});
|
||||
|
||||
it('should sort by name', () => {
|
||||
let file1 = new FileNode('file1');
|
||||
let file2 = new FileNode('file11');
|
||||
let file3 = new FileNode('file20');
|
||||
let file4 = new FileNode('file11-1'); // auto rename
|
||||
let file5 = new FileNode('a');
|
||||
let file6 = new FileNode('b');
|
||||
|
||||
let col = <DataColumn> {key: 'name'};
|
||||
let adapter = new ShareDataTableAdapter(documentListService, [col]);
|
||||
|
||||
adapter.setRows([
|
||||
new ShareDataRow(file4, documentListService, null),
|
||||
new ShareDataRow(file6, documentListService, null),
|
||||
new ShareDataRow(file3, documentListService, null),
|
||||
new ShareDataRow(file1, documentListService, null),
|
||||
new ShareDataRow(file2, documentListService, null),
|
||||
new ShareDataRow(file5, documentListService, null)
|
||||
]);
|
||||
|
||||
adapter.sort('name', 'asc');
|
||||
let rows = adapter.getRows();
|
||||
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file5);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file6);
|
||||
expect((<ShareDataRow> rows[2]).node).toBe(file1);
|
||||
expect((<ShareDataRow> rows[3]).node).toBe(file2);
|
||||
expect((<ShareDataRow> rows[4]).node).toBe(file4);
|
||||
expect((<ShareDataRow> rows[5]).node).toBe(file3);
|
||||
|
||||
adapter.sort('name', 'desc');
|
||||
expect((<ShareDataRow> rows[0]).node).toBe(file3);
|
||||
expect((<ShareDataRow> rows[1]).node).toBe(file4);
|
||||
expect((<ShareDataRow> rows[2]).node).toBe(file2);
|
||||
expect((<ShareDataRow> rows[3]).node).toBe(file1);
|
||||
expect((<ShareDataRow> rows[4]).node).toBe(file6);
|
||||
expect((<ShareDataRow> rows[5]).node).toBe(file5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ShareDataRow', () => {
|
||||
|
||||
let documentListService: DocumentListService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DocumentListService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
documentListService = TestBed.get(DocumentListService);
|
||||
});
|
||||
|
||||
it('should wrap node', () => {
|
||||
let file = new FileNode();
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
expect(row.node).toBe(file);
|
||||
});
|
||||
|
||||
it('should require object source', () => {
|
||||
expect(() => {
|
||||
return new ShareDataRow(null, documentListService, null);
|
||||
}).toThrowError(ShareDataRow.ERR_OBJECT_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should resolve value from node entry', () => {
|
||||
let file = new FileNode('test');
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
expect(row.getValue('name')).toBe('test');
|
||||
});
|
||||
|
||||
it('should check value', () => {
|
||||
let file = new FileNode('test');
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
|
||||
expect(row.hasValue('name')).toBeTruthy();
|
||||
expect(row.hasValue('missing')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should be set as drop target when user has permission for that node', () => {
|
||||
let file = new FolderNode('test');
|
||||
file.entry['allowableOperations'] = ['create'];
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
|
||||
expect(row.isDropTarget).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be set as drop target when user has permission for that node', () => {
|
||||
let file = new FolderNode('test');
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
|
||||
expect(row.isDropTarget).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not be set as drop target when element is not a Folder', () => {
|
||||
let file = new FileNode('test');
|
||||
let row = new ShareDataRow(file, documentListService, null);
|
||||
|
||||
expect(row.isDropTarget).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,252 @@
|
||||
/*!
|
||||
* @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 { DataColumn, DataRow, DataSorting, DataTableAdapter } from '@alfresco/core';
|
||||
import { TimeAgoPipe } from '@alfresco/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { NodePaging } from 'alfresco-js-api';
|
||||
import { PermissionStyleModel } from './../models/permissions-style.model';
|
||||
import { DocumentListService } from './../services/document-list.service';
|
||||
import { ImageResolver } from './image-resolver.model';
|
||||
import { RowFilter } from './row-filter.model';
|
||||
import { ShareDataRow } from './share-data-row.model';
|
||||
|
||||
export class ShareDataTableAdapter implements DataTableAdapter {
|
||||
|
||||
ERR_ROW_NOT_FOUND: string = 'Row not found';
|
||||
ERR_COL_NOT_FOUND: string = 'Column not found';
|
||||
|
||||
private sorting: DataSorting;
|
||||
private rows: DataRow[];
|
||||
private columns: DataColumn[];
|
||||
private page: NodePaging;
|
||||
|
||||
private filter: RowFilter;
|
||||
private imageResolver: ImageResolver;
|
||||
|
||||
thumbnails: boolean = false;
|
||||
permissionsStyle: PermissionStyleModel[];
|
||||
selectedRow: DataRow;
|
||||
|
||||
constructor(private documentListService: DocumentListService,
|
||||
schema: DataColumn[] = [],
|
||||
sorting?: DataSorting) {
|
||||
this.rows = [];
|
||||
this.columns = schema || [];
|
||||
this.sorting = sorting;
|
||||
}
|
||||
|
||||
getRows(): Array<DataRow> {
|
||||
return this.rows;
|
||||
}
|
||||
|
||||
// TODO: disable this api
|
||||
setRows(rows: Array<DataRow>) {
|
||||
this.rows = rows || [];
|
||||
this.sort();
|
||||
}
|
||||
|
||||
getColumns(): Array<DataColumn> {
|
||||
return this.columns;
|
||||
}
|
||||
|
||||
setColumns(columns: Array<DataColumn>) {
|
||||
this.columns = columns || [];
|
||||
}
|
||||
|
||||
getValue(row: DataRow, col: DataColumn): any {
|
||||
if (!row) {
|
||||
throw new Error(this.ERR_ROW_NOT_FOUND);
|
||||
}
|
||||
if (!col) {
|
||||
throw new Error(this.ERR_COL_NOT_FOUND);
|
||||
}
|
||||
let dataRow: ShareDataRow = <ShareDataRow> row;
|
||||
let value: any = row.getValue(col.key);
|
||||
if (dataRow.cache[col.key] !== undefined) {
|
||||
return dataRow.cache[col.key];
|
||||
}
|
||||
|
||||
if (col.type === 'date') {
|
||||
try {
|
||||
const result = this.formatDate(col, value);
|
||||
return dataRow.cacheValue(col.key, result);
|
||||
} catch (err) {
|
||||
console.error(`Error parsing date ${value} to format ${col.format}`);
|
||||
return 'Error';
|
||||
}
|
||||
}
|
||||
|
||||
if (col.key === '$thumbnail') {
|
||||
|
||||
if (this.imageResolver) {
|
||||
let resolved = this.imageResolver(row, col);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
const node = (<ShareDataRow> row).node;
|
||||
|
||||
if (node.entry.isFolder) {
|
||||
return this.documentListService.getMimeTypeIcon('folder');
|
||||
}
|
||||
|
||||
if (node.entry.isFile) {
|
||||
if (this.thumbnails) {
|
||||
return this.documentListService.getDocumentThumbnailUrl(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.entry.content) {
|
||||
const mimeType = node.entry.content.mimeType;
|
||||
if (mimeType) {
|
||||
return this.documentListService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
return this.documentListService.getDefaultMimeTypeIcon();
|
||||
}
|
||||
|
||||
if (col.type === 'image') {
|
||||
|
||||
if (this.imageResolver) {
|
||||
let resolved = this.imageResolver(row, col);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataRow.cacheValue(col.key, value);
|
||||
}
|
||||
|
||||
formatDate(col: DataColumn, value: any): string {
|
||||
if (col.type === 'date') {
|
||||
const format = col.format || 'medium';
|
||||
if (format === 'timeAgo') {
|
||||
const timeAgoPipe = new TimeAgoPipe();
|
||||
return timeAgoPipe.transform(value);
|
||||
} else {
|
||||
const datePipe = new DatePipe('en-US');
|
||||
return datePipe.transform(value, format);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getSorting(): DataSorting {
|
||||
return this.sorting;
|
||||
}
|
||||
|
||||
setSorting(sorting: DataSorting): void {
|
||||
this.sorting = sorting;
|
||||
|
||||
this.sortRows(this.rows, this.sorting);
|
||||
}
|
||||
|
||||
sort(key?: string, direction?: string): void {
|
||||
let sorting = this.sorting || new DataSorting();
|
||||
if (key) {
|
||||
sorting.key = key;
|
||||
sorting.direction = direction || 'asc';
|
||||
}
|
||||
this.setSorting(sorting);
|
||||
}
|
||||
|
||||
setFilter(filter: RowFilter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
setImageResolver(resolver: ImageResolver) {
|
||||
this.imageResolver = resolver;
|
||||
}
|
||||
|
||||
private sortRows(rows: DataRow[], sorting: DataSorting) {
|
||||
const options: Intl.CollatorOptions = {};
|
||||
|
||||
if (sorting && sorting.key && rows && rows.length > 0) {
|
||||
|
||||
if (sorting.key.includes('sizeInBytes') || sorting.key === 'name') {
|
||||
options.numeric = true;
|
||||
}
|
||||
|
||||
rows.sort((a: ShareDataRow, b: ShareDataRow) => {
|
||||
if (a.node.entry.isFolder !== b.node.entry.isFolder) {
|
||||
return a.node.entry.isFolder ? -1 : 1;
|
||||
}
|
||||
|
||||
let left = a.getValue(sorting.key);
|
||||
if (left) {
|
||||
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
|
||||
} else {
|
||||
left = '';
|
||||
}
|
||||
|
||||
let right = b.getValue(sorting.key);
|
||||
if (right) {
|
||||
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
|
||||
} else {
|
||||
right = '';
|
||||
}
|
||||
|
||||
return sorting.direction === 'asc'
|
||||
? left.localeCompare(right, undefined, options)
|
||||
: right.localeCompare(left, undefined, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public loadPage(page: NodePaging, merge: boolean = false) {
|
||||
this.page = page;
|
||||
|
||||
let rows = [];
|
||||
|
||||
if (page && page.list) {
|
||||
let data = page.list.entries;
|
||||
if (data && data.length > 0) {
|
||||
rows = data.map(item => new ShareDataRow(item, this.documentListService, this.permissionsStyle));
|
||||
|
||||
if (this.filter) {
|
||||
rows = rows.filter(this.filter);
|
||||
}
|
||||
|
||||
// Sort by first sortable or just first column
|
||||
if (this.columns && this.columns.length > 0) {
|
||||
let sorting = this.getSorting();
|
||||
if (sorting) {
|
||||
this.sortRows(rows, sorting);
|
||||
} else {
|
||||
let sortable = this.columns.filter(c => c.sortable);
|
||||
if (sortable.length > 0) {
|
||||
this.sort(sortable[0].key, 'asc');
|
||||
} else {
|
||||
this.sort(this.columns[0].key, 'asc');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (merge) {
|
||||
this.rows = this.rows.concat(rows);
|
||||
} else {
|
||||
this.rows = rows;
|
||||
}
|
||||
}
|
||||
}
|
77
lib/content-services/document-list/document-list.module.ts
Normal file
77
lib/content-services/document-list/document-list.module.ts
Normal 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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DataTableModule, PaginationModule, ToolbarModule } from '@alfresco/core';
|
||||
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { UploadModule } from '../upload';
|
||||
|
||||
import { ContentActionListComponent } from './components/content-action/content-action-list.component';
|
||||
import { ContentActionComponent } from './components/content-action/content-action.component';
|
||||
import { ContentColumnListComponent } from './components/content-column/content-column-list.component';
|
||||
import { ContentColumnComponent } from './components/content-column/content-column.component';
|
||||
import { DocumentListComponent } from './components/document-list.component';
|
||||
|
||||
import { EmptyFolderContentDirective } from './components/empty-folder/empty-folder-content.directive';
|
||||
import { NoPermissionContentDirective } from './components/no-permission/no-permission-content.directive';
|
||||
|
||||
import { DocumentActionsService } from './services/document-actions.service';
|
||||
import { DocumentListService } from './services/document-list.service';
|
||||
import { FolderActionsService } from './services/folder-actions.service';
|
||||
import { NodeActionsService } from './services/node-actions.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ToolbarModule,
|
||||
CommonModule,
|
||||
DataTableModule,
|
||||
FlexLayoutModule,
|
||||
MaterialModule,
|
||||
UploadModule,
|
||||
TranslateModule,
|
||||
PaginationModule
|
||||
],
|
||||
declarations: [
|
||||
DocumentListComponent,
|
||||
ContentColumnComponent,
|
||||
ContentColumnListComponent,
|
||||
ContentActionComponent,
|
||||
ContentActionListComponent,
|
||||
EmptyFolderContentDirective,
|
||||
NoPermissionContentDirective
|
||||
],
|
||||
providers: [
|
||||
DocumentListService,
|
||||
FolderActionsService,
|
||||
DocumentActionsService,
|
||||
NodeActionsService
|
||||
],
|
||||
exports: [
|
||||
DocumentListComponent,
|
||||
ContentColumnComponent,
|
||||
ContentColumnListComponent,
|
||||
ContentActionComponent,
|
||||
ContentActionListComponent,
|
||||
EmptyFolderContentDirective,
|
||||
NoPermissionContentDirective
|
||||
]
|
||||
})
|
||||
export class DocumentListModule {}
|
18
lib/content-services/document-list/index.ts
Normal file
18
lib/content-services/document-list/index.ts
Normal 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';
|
@@ -0,0 +1,56 @@
|
||||
/*!
|
||||
* @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 class ContentActionModel {
|
||||
icon: string;
|
||||
title: string;
|
||||
handler: ContentActionHandler;
|
||||
execute: Function;
|
||||
target: string;
|
||||
permission: string;
|
||||
disableWithNoPermission: boolean = false;
|
||||
disabled: boolean = false;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.icon = obj.icon;
|
||||
this.title = obj.title;
|
||||
this.handler = obj.handler;
|
||||
this.execute = obj.execute;
|
||||
this.target = obj.target;
|
||||
this.permission = obj.permission;
|
||||
this.disableWithNoPermission = obj.disableWithNoPermission;
|
||||
this.disabled = obj.disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ContentActionHandler = (obj: any, target?: any, permission?: string) => any;
|
||||
|
||||
export class DocumentActionModel extends ContentActionModel {
|
||||
constructor(json?: any) {
|
||||
super(json);
|
||||
this.target = 'document';
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderActionModel extends ContentActionModel {
|
||||
constructor(json?: any) {
|
||||
super(json);
|
||||
this.target = 'folder';
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
// note: contains only limited subset of available fields
|
||||
|
||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
export class NodePaging {
|
||||
list: NodePagingList;
|
||||
}
|
||||
|
||||
export class NodePagingList {
|
||||
pagination: Pagination;
|
||||
entries: NodeMinimalEntry[];
|
||||
}
|
||||
|
||||
export class NodeMinimalEntry implements MinimalNodeEntity {
|
||||
entry: NodeMinimal;
|
||||
}
|
||||
|
||||
export class Pagination {
|
||||
count: number;
|
||||
hasMoreItems: boolean;
|
||||
totalItems: number;
|
||||
skipCount: number;
|
||||
maxItems: number;
|
||||
}
|
||||
|
||||
export class NodeMinimal implements MinimalNodeEntryEntity {
|
||||
id: string;
|
||||
parentId: string;
|
||||
name: string;
|
||||
nodeType: string;
|
||||
isFolder: boolean;
|
||||
isFile: boolean;
|
||||
modifiedAt: Date;
|
||||
modifiedByUser: UserInfo;
|
||||
createdAt: Date;
|
||||
createdByUser: UserInfo;
|
||||
content: ContentInfo;
|
||||
path: PathInfoEntity;
|
||||
properties: NodeProperties = {};
|
||||
}
|
||||
|
||||
export class UserInfo {
|
||||
displayName: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class ContentInfo {
|
||||
mimeType: string;
|
||||
mimeTypeName: string;
|
||||
sizeInBytes: number;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
export class PathInfoEntity {
|
||||
elements: PathElementEntity[];
|
||||
isComplete: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class PathElementEntity {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface NodeProperties {
|
||||
[key: string]: any;
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* @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 { PermissionsEnum } from '@alfresco/core';
|
||||
|
||||
export class PermissionStyleModel {
|
||||
css: string;
|
||||
permission: PermissionsEnum;
|
||||
isFolder: boolean = true;
|
||||
isFile: boolean = true;
|
||||
|
||||
constructor(css: string, permission: PermissionsEnum, isFile: boolean = true, isFolder: boolean = true) {
|
||||
this.css = css;
|
||||
this.permission = permission;
|
||||
this.isFile = isFile;
|
||||
this.isFolder = isFolder;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* @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 class PermissionModel {
|
||||
type: string;
|
||||
action: string;
|
||||
permission: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.type = obj.type || null;
|
||||
this.action = obj.action || null;
|
||||
this.permission = obj.permission || null;
|
||||
}
|
||||
}
|
||||
}
|
261
lib/content-services/document-list/models/preset.model.ts
Normal file
261
lib/content-services/document-list/models/preset.model.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
/*!
|
||||
* @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 let presetsDefaultModel = {
|
||||
'-trashcan-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
type: 'location',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.LOCATION',
|
||||
format: this.locationFormat,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'content.sizeInBytes',
|
||||
type: 'fileSize',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SIZE',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'archivedAt',
|
||||
type: 'date',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.DELETED_ON',
|
||||
format: 'timeAgo',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'archivedByUser.displayName',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.DELETED_BY',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'-sites-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'visibility',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.STATUS',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'-mysites-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'visibility',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.STATUS',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'-favorites-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
type: 'location',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.LOCATION',
|
||||
format: this.locationFormat,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'content.sizeInBytes',
|
||||
type: 'fileSize',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SIZE',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedAt',
|
||||
type: 'date',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_ON',
|
||||
format: 'timeAgo',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedByUser.displayName',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_BY',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'-recent-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
type: 'location',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.LOCATION',
|
||||
cssClass: 'ellipsis-cell',
|
||||
format: this.locationFormat,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'content.sizeInBytes',
|
||||
type: 'fileSize',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SIZE',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedAt',
|
||||
type: 'date',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_ON',
|
||||
format: 'timeAgo',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'-sharedlinks-': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
type: 'location',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.LOCATION',
|
||||
cssClass: 'ellipsis-cell',
|
||||
format: this.locationFormat,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'content.sizeInBytes',
|
||||
type: 'fileSize',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SIZE',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedAt',
|
||||
type: 'date',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_ON',
|
||||
format: 'timeAgo',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedByUser.displayName',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_BY',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'sharedByUser.displayName',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SHARED_BY',
|
||||
sortable: true
|
||||
}
|
||||
],
|
||||
'default': [
|
||||
{
|
||||
key: '$thumbnail',
|
||||
type: 'image',
|
||||
srTitle: 'ADF-DOCUMENT-LIST.LAYOUT.THUMBNAIL',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.NAME',
|
||||
cssClass: 'full-width ellipsis-cell',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'content.sizeInBytes',
|
||||
type: 'fileSize',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.SIZE',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedAt',
|
||||
type: 'date',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_ON',
|
||||
format: 'timeAgo',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: 'modifiedByUser.displayName',
|
||||
type: 'text',
|
||||
title: 'ADF-DOCUMENT-LIST.LAYOUT.MODIFIED_BY',
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
};
|
45
lib/content-services/document-list/public-api.ts
Normal file
45
lib/content-services/document-list/public-api.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @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 './components/document-list.component';
|
||||
export * from './components/node.event';
|
||||
export * from './components/content-column/content-column.component';
|
||||
export * from './components/content-column/content-column-list.component';
|
||||
export * from './components/content-action/content-action.component';
|
||||
export * from './components/content-action/content-action-list.component';
|
||||
export * from './components/empty-folder/empty-folder-content.directive';
|
||||
export * from './components/no-permission/no-permission-content.directive';
|
||||
|
||||
// data
|
||||
export * from './data/share-datatable-adapter';
|
||||
export * from './data/share-data-row.model';
|
||||
export * from './data/image-resolver.model';
|
||||
export * from './data/row-filter.model';
|
||||
|
||||
// services
|
||||
export * from './services/folder-actions.service';
|
||||
export * from './services/document-actions.service';
|
||||
export * from './services/document-list.service';
|
||||
export * from './services/node-actions.service';
|
||||
|
||||
// models
|
||||
export * from './models/content-action.model';
|
||||
export * from './models/document-library.model';
|
||||
export * from './models/permissions.model';
|
||||
export * from './models/permissions-style.model';
|
||||
|
||||
export * from './document-list.module';
|
@@ -0,0 +1,285 @@
|
||||
/*!
|
||||
* @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 { ContentService, TranslationService, NotificationService } from '@alfresco/core';
|
||||
import { FileNode, FolderNode, DocumentListServiceMock } from '../../mock';
|
||||
import { ContentActionHandler } from '../models/content-action.model';
|
||||
import { DocumentActionsService } from './document-actions.service';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
import { NodeActionsService } from './node-actions.service';
|
||||
|
||||
describe('DocumentActionsService', () => {
|
||||
|
||||
let service: DocumentActionsService;
|
||||
let documentListService: DocumentListService;
|
||||
let contentService: ContentService;
|
||||
let translateService: TranslationService;
|
||||
let notificationService: NotificationService;
|
||||
let nodeActionsService: NodeActionsService;
|
||||
|
||||
beforeEach(() => {
|
||||
documentListService = new DocumentListServiceMock();
|
||||
contentService = new ContentService(null, null, null, null);
|
||||
translateService = <TranslationService> { addTranslationFolder: () => {}};
|
||||
nodeActionsService = new NodeActionsService(null, null, null);
|
||||
notificationService = new NotificationService(null);
|
||||
service = new DocumentActionsService(nodeActionsService, documentListService, contentService);
|
||||
});
|
||||
|
||||
it('should register default download action', () => {
|
||||
expect(service.getHandler('download')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should register custom action handler', () => {
|
||||
let handler: ContentActionHandler = function (obj: any) {};
|
||||
service.setHandler('<key>', handler);
|
||||
expect(service.getHandler('<key>')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should not find handler that is not registered', () => {
|
||||
expect(service.getHandler('<missing>')).toBeNull();
|
||||
});
|
||||
|
||||
it('should be case insensitive for keys', () => {
|
||||
let handler: ContentActionHandler = function (obj: any) {};
|
||||
service.setHandler('<key>', handler);
|
||||
expect(service.getHandler('<KEY>')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should not find handler with invalid key', () => {
|
||||
expect(service.getHandler(null)).toBeNull();
|
||||
expect(service.getHandler('')).toBeNull();
|
||||
});
|
||||
|
||||
it('should allow action execution only when service available', () => {
|
||||
let file = new FileNode();
|
||||
expect(service.canExecuteAction(file)).toBeTruthy();
|
||||
|
||||
service = new DocumentActionsService(nodeActionsService);
|
||||
expect(service.canExecuteAction(file)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should allow action execution only for file nodes', () => {
|
||||
expect(service.canExecuteAction(null)).toBeFalsy();
|
||||
expect(service.canExecuteAction(new FileNode())).toBeTruthy();
|
||||
expect(service.canExecuteAction(new FolderNode())).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set new handler only by key', () => {
|
||||
let handler: ContentActionHandler = function (obj: any) {};
|
||||
expect(service.setHandler(null, handler)).toBeFalsy();
|
||||
expect(service.setHandler('', handler)).toBeFalsy();
|
||||
expect(service.setHandler('my-handler', handler)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should register delete action', () => {
|
||||
expect(service.getHandler('delete')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not delete the file node if there are no permissions', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
service.permissionEvent.subscribe((permission) => {
|
||||
expect(permission).toBeDefined();
|
||||
expect(permission.type).toEqual('content');
|
||||
expect(permission.action).toEqual('delete');
|
||||
done();
|
||||
});
|
||||
|
||||
let file = new FileNode();
|
||||
service.getHandler('delete')(file);
|
||||
|
||||
});
|
||||
|
||||
it('should call the error on the returned Observable if there are no permissions', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let file = new FileNode();
|
||||
const deleteObservable = service.getHandler('delete')(file);
|
||||
|
||||
deleteObservable.subscribe({
|
||||
error: (error) => {
|
||||
expect(error.message).toEqual('No permission to delete');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the file node if there is the delete permission', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = [permission];
|
||||
service.getHandler('delete')(fileWithPermission, null, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
|
||||
});
|
||||
|
||||
it('should not delete the file node if there is no delete permission', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
service.permissionEvent.subscribe((permissionBack) => {
|
||||
expect(permissionBack).toBeDefined();
|
||||
expect(permissionBack.type).toEqual('content');
|
||||
expect(permissionBack.action).toEqual('delete');
|
||||
done();
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = ['create', 'update'];
|
||||
service.getHandler('delete')(fileWithPermission, null, permission);
|
||||
});
|
||||
|
||||
it('should delete the file node if there is the delete and others permission ', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = ['create', 'update', permission];
|
||||
service.getHandler('delete')(fileWithPermission, null, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
|
||||
});
|
||||
|
||||
it('should register download action', () => {
|
||||
expect(service.getHandler('download')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should execute download action and cleanup', () => {
|
||||
let file = new FileNode();
|
||||
let url = 'http://<address>';
|
||||
|
||||
spyOn(contentService, 'getContentUrl').and.returnValue(url);
|
||||
|
||||
let link = jasmine.createSpyObj('a', [
|
||||
'setAttribute',
|
||||
'click'
|
||||
]);
|
||||
|
||||
spyOn(document, 'createElement').and.returnValue(link);
|
||||
spyOn(document.body, 'appendChild').and.stub();
|
||||
spyOn(document.body, 'removeChild').and.stub();
|
||||
|
||||
service.getHandler('download')(file);
|
||||
|
||||
expect(contentService.getContentUrl).toHaveBeenCalledWith(file);
|
||||
expect(document.createElement).toHaveBeenCalledWith('a');
|
||||
expect(link.setAttribute).toHaveBeenCalledWith('download', 'download');
|
||||
expect(document.body.appendChild).toHaveBeenCalledWith(link);
|
||||
expect(link.click).toHaveBeenCalled();
|
||||
expect(document.body.removeChild).toHaveBeenCalledWith(link);
|
||||
});
|
||||
|
||||
it('should require internal service for download action', () => {
|
||||
let actionService = new DocumentActionsService(nodeActionsService, null, contentService);
|
||||
let file = new FileNode();
|
||||
let result = actionService.getHandler('download')(file);
|
||||
result.subscribe((value) => {
|
||||
expect(value).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require content service for download action', () => {
|
||||
let actionService = new DocumentActionsService(nodeActionsService, documentListService, null);
|
||||
let file = new FileNode();
|
||||
let result = actionService.getHandler('download')(file);
|
||||
result.subscribe((value) => {
|
||||
expect(value).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require file node for download action', () => {
|
||||
let folder = new FolderNode();
|
||||
let result = service.getHandler('download')(folder);
|
||||
result.subscribe((value) => {
|
||||
expect(value).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete file node', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = [permission];
|
||||
const deleteObservale = service.getHandler('delete')(fileWithPermission, null, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
|
||||
expect(deleteObservale.subscribe).toBeDefined();
|
||||
});
|
||||
|
||||
it('should support deletion only file node', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let folder = new FolderNode();
|
||||
service.getHandler('delete')(folder);
|
||||
expect(documentListService.deleteNode).not.toHaveBeenCalled();
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = [permission];
|
||||
service.getHandler('delete')(fileWithPermission, null, permission);
|
||||
expect(documentListService.deleteNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require node id to delete', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let file = new FileNode();
|
||||
file.entry.id = null;
|
||||
service.getHandler('delete')(file);
|
||||
|
||||
expect(documentListService.deleteNode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reload target upon node deletion', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let target = jasmine.createSpyObj('obj', ['reload']);
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = [permission];
|
||||
service.getHandler('delete')(fileWithPermission, target, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalled();
|
||||
expect(target.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit success event upon node deletion', (done) => {
|
||||
service.success.subscribe((nodeId) => {
|
||||
expect(nodeId).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
let target = jasmine.createSpyObj('obj', ['reload']);
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
let fileWithPermission: any = file;
|
||||
fileWithPermission.entry.allowableOperations = [permission];
|
||||
service.getHandler('delete')(fileWithPermission, target, permission);
|
||||
|
||||
});
|
||||
});
|
@@ -0,0 +1,127 @@
|
||||
/*!
|
||||
* @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 { ContentService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Rx';
|
||||
import { ContentActionHandler } from '../models/content-action.model';
|
||||
import { PermissionModel } from '../models/permissions.model';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
import { NodeActionsService } from './node-actions.service';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentActionsService {
|
||||
|
||||
permissionEvent: Subject<PermissionModel> = new Subject<PermissionModel>();
|
||||
error: Subject<Error> = new Subject<Error>();
|
||||
success: Subject<string> = new Subject<string>();
|
||||
|
||||
private handlers: { [id: string]: ContentActionHandler; } = {};
|
||||
|
||||
constructor(private nodeActionsService: NodeActionsService,
|
||||
private documentListService?: DocumentListService,
|
||||
private contentService?: ContentService) {
|
||||
this.setupActionHandlers();
|
||||
}
|
||||
|
||||
getHandler(key: string): ContentActionHandler {
|
||||
if (key) {
|
||||
let lkey = key.toLowerCase();
|
||||
return this.handlers[lkey] || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setHandler(key: string, handler: ContentActionHandler): boolean {
|
||||
if (key) {
|
||||
let lkey = key.toLowerCase();
|
||||
this.handlers[lkey] = handler;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canExecuteAction(obj: any): boolean {
|
||||
return this.documentListService && obj && obj.entry.isFile === true;
|
||||
}
|
||||
|
||||
private setupActionHandlers() {
|
||||
this.handlers['download'] = this.download.bind(this);
|
||||
this.handlers['copy'] = this.copyNode.bind(this);
|
||||
this.handlers['move'] = this.moveNode.bind(this);
|
||||
this.handlers['delete'] = this.deleteNode.bind(this);
|
||||
}
|
||||
|
||||
private download(obj: any): Observable<boolean> {
|
||||
if (this.canExecuteAction(obj) && this.contentService) {
|
||||
let link = document.createElement('a');
|
||||
document.body.appendChild(link);
|
||||
link.setAttribute('download', 'download');
|
||||
link.href = this.contentService.getContentUrl(obj);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
return Observable.of(true);
|
||||
}
|
||||
return Observable.of(false);
|
||||
}
|
||||
|
||||
private copyNode(obj: MinimalNodeEntity, target?: any, permission?: string) {
|
||||
const actionObservable = this.nodeActionsService.copyContent(obj.entry, permission);
|
||||
this.prepareHandlers(actionObservable, 'content', 'copy', target, permission);
|
||||
return actionObservable;
|
||||
}
|
||||
|
||||
private moveNode(obj: MinimalNodeEntity, target?: any, permission?: string) {
|
||||
const actionObservable = this.nodeActionsService.moveContent(obj.entry, permission);
|
||||
this.prepareHandlers(actionObservable, 'content', 'move', target, permission);
|
||||
return actionObservable;
|
||||
}
|
||||
|
||||
private prepareHandlers(actionObservable, type: string, action: string, target?: any, permission?: string): void {
|
||||
actionObservable.subscribe(
|
||||
(fileOperationMessage) => {
|
||||
if (target && typeof target.reload === 'function') {
|
||||
target.reload();
|
||||
}
|
||||
this.success.next(fileOperationMessage);
|
||||
},
|
||||
this.error.next.bind(this.error)
|
||||
);
|
||||
}
|
||||
|
||||
private deleteNode(obj: any, target?: any, permission?: string): Observable<any> {
|
||||
let handlerObservable;
|
||||
|
||||
if (this.canExecuteAction(obj)) {
|
||||
if (this.contentService.hasPermission(obj.entry, permission)) {
|
||||
handlerObservable = this.documentListService.deleteNode(obj.entry.id);
|
||||
handlerObservable.subscribe(() => {
|
||||
if (target && typeof target.reload === 'function') {
|
||||
target.reload();
|
||||
}
|
||||
this.success.next(obj.entry.id);
|
||||
});
|
||||
return handlerObservable;
|
||||
} else {
|
||||
this.permissionEvent.next(new PermissionModel({type: 'content', action: 'delete', permission: permission}));
|
||||
return Observable.throw(new Error('No permission to delete'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,198 @@
|
||||
/*!
|
||||
* @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 { async, TestBed } from '@angular/core/testing';
|
||||
import { CookieService, LogService } from '@alfresco/core';
|
||||
import { CookieServiceMock } from '@alfresco/core';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('DocumentListService', () => {
|
||||
|
||||
let service: DocumentListService;
|
||||
|
||||
let fakeEntryNode = {
|
||||
'entry': {
|
||||
'aspectNames': ['cm:auditable'],
|
||||
'createdAt': '2016-12-06T15:58:32.408+0000',
|
||||
'isFolder': true,
|
||||
'isFile': false,
|
||||
'createdByUser': {'id': 'admin', 'displayName': 'Administrator'},
|
||||
'modifiedAt': '2016-12-06T15:58:32.408+0000',
|
||||
'modifiedByUser': {'id': 'admin', 'displayName': 'Administrator'},
|
||||
'name': 'fake-name',
|
||||
'id': '2214733d-a920-4dbe-af95-4230345fae82',
|
||||
'nodeType': 'cm:folder',
|
||||
'parentId': 'ed7ab80e-b398-4bed-b38d-139ae4cc592a'
|
||||
}
|
||||
};
|
||||
|
||||
let fakeAlreadyExist = {
|
||||
'error': {
|
||||
'errorKey': 'Duplicate child name not allowed: empty',
|
||||
'statusCode': 409,
|
||||
'briefSummary': '11060002 Duplicate child name not' +
|
||||
' allowed: empty',
|
||||
'stackTrace': 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.',
|
||||
'descriptionURL': 'https://api-explorer.alfresco.com'
|
||||
}
|
||||
};
|
||||
|
||||
let fakeFolder = {
|
||||
'list': {
|
||||
'pagination': {'count': 1, 'hasMoreItems': false, 'totalItems': 1, 'skipCount': 0, 'maxItems': 20},
|
||||
'entries': [{
|
||||
'entry': {
|
||||
'createdAt': '2016-12-06T13:03:14.880+0000',
|
||||
'path': {
|
||||
'name': '/Company Home/Sites/swsdp/documentLibrary/empty',
|
||||
'isComplete': true,
|
||||
'elements': [{
|
||||
'id': 'ed7ab80e-b398-4bed-b38d-139ae4cc592a',
|
||||
'name': 'Company Home'
|
||||
}, {'id': '99e1368f-e816-47fc-a8bf-3b358feaf31e', 'name': 'Sites'}, {
|
||||
'id': 'b4cff62a-664d-4d45-9302-98723eac1319',
|
||||
'name': 'swsdp'
|
||||
}, {
|
||||
'id': '8f2105b4-daaf-4874-9e8a-2152569d109b',
|
||||
'name': 'documentLibrary'
|
||||
}, {'id': '17fa78d2-4d6b-4a46-876b-4b0ea07f7f32', 'name': 'empty'}]
|
||||
},
|
||||
'isFolder': true,
|
||||
'isFile': false,
|
||||
'createdByUser': {'id': 'admin', 'displayName': 'Administrator'},
|
||||
'modifiedAt': '2016-12-06T13:03:14.880+0000',
|
||||
'modifiedByUser': {'id': 'admin', 'displayName': 'Administrator'},
|
||||
'name': 'fake-name',
|
||||
'id': 'aac546f6-1525-46ff-bf6b-51cb85f3cda7',
|
||||
'nodeType': 'cm:folder',
|
||||
'parentId': '17fa78d2-4d6b-4a46-876b-4b0ea07f7f32'
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DocumentListService,
|
||||
{ provide: CookieService, useClass: CookieServiceMock },
|
||||
LogService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.get(DocumentListService);
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should create a folder in the path', () => {
|
||||
service.createFolder('fake-name', 'fake-path').subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.entry).toBeDefined();
|
||||
expect(res.entry.isFolder).toBeTruthy();
|
||||
expect(res.entry.name).toEqual('fake-name');
|
||||
expect(res.entry.nodeType).toEqual('cm:folder');
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeEntryNode
|
||||
});
|
||||
});
|
||||
|
||||
xit('should emit an error when the folder already exist', () => {
|
||||
service.createFolder('fake-name', 'fake-path').subscribe(
|
||||
res => {
|
||||
|
||||
},
|
||||
err => {
|
||||
expect(err).toBeDefined();
|
||||
expect(err.status).toEqual(409);
|
||||
expect(err.response).toBeDefined();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 409,
|
||||
contentType: 'json',
|
||||
responseText: fakeAlreadyExist
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the folder info', () => {
|
||||
service.getFolder('/fake-root/fake-name').subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.list).toBeDefined();
|
||||
expect(res.list.entries).toBeDefined();
|
||||
expect(res.list.entries.length).toBe(1);
|
||||
expect(res.list.entries[0].entry.isFolder).toBeTruthy();
|
||||
expect(res.list.entries[0].entry.name).toEqual('fake-name');
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: fakeFolder
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the folder', () => {
|
||||
service.deleteNode('fake-id').subscribe(
|
||||
res => {
|
||||
expect(res).toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 204,
|
||||
contentType: 'json'
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy a node', (done) => {
|
||||
service.copyNode('node-id', 'parent-id').subscribe(done);
|
||||
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('/nodes/node-id/copy');
|
||||
expect(jasmine.Ajax.requests.mostRecent().params).toEqual(JSON.stringify({ targetParentId: 'parent-id' }));
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, contentType: 'json' });
|
||||
});
|
||||
|
||||
it('should move a node', (done) => {
|
||||
service.moveNode('node-id', 'parent-id').subscribe(done);
|
||||
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('/nodes/node-id/move');
|
||||
expect(jasmine.Ajax.requests.mostRecent().params).toEqual(JSON.stringify({ targetParentId: 'parent-id' }));
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, contentType: 'json' });
|
||||
});
|
||||
});
|
@@ -0,0 +1,150 @@
|
||||
/*!
|
||||
* @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 { AlfrescoApiService, AuthenticationService, ContentService, LogService, PermissionsEnum, ThumbnailService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Response } from '@angular/http';
|
||||
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentListService {
|
||||
|
||||
static ROOT_ID = '-root-';
|
||||
|
||||
constructor(authService: AuthenticationService,
|
||||
private contentService: ContentService,
|
||||
private apiService: AlfrescoApiService,
|
||||
private logService: LogService,
|
||||
private thumbnailService: ThumbnailService) {
|
||||
}
|
||||
|
||||
private getNodesPromise(folder: string, opts?: any): Promise<NodePaging> {
|
||||
|
||||
let rootNodeId = DocumentListService.ROOT_ID;
|
||||
if (opts && opts.rootFolderId) {
|
||||
rootNodeId = opts.rootFolderId;
|
||||
}
|
||||
|
||||
let params: any = {
|
||||
includeSource: true,
|
||||
include: ['path', 'properties', 'allowableOperations']
|
||||
};
|
||||
|
||||
if (folder) {
|
||||
params.relativePath = folder;
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
if (opts.maxItems) {
|
||||
params.maxItems = opts.maxItems;
|
||||
}
|
||||
if (opts.skipCount) {
|
||||
params.skipCount = opts.skipCount;
|
||||
}
|
||||
}
|
||||
|
||||
return this.apiService.getInstance().nodes.getNodeChildren(rootNodeId, params);
|
||||
}
|
||||
|
||||
deleteNode(nodeId: string): Observable<any> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().nodes.deleteNode(nodeId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a node to destination node
|
||||
*
|
||||
* @param nodeId The id of the node to be copied
|
||||
* @param targetParentId The id of the folder-node where the node have to be copied to
|
||||
*/
|
||||
copyNode(nodeId: string, targetParentId: string) {
|
||||
return Observable.fromPromise(this.apiService.getInstance().nodes.copyNode(nodeId, { targetParentId }))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a node to destination node
|
||||
*
|
||||
* @param nodeId The id of the node to be moved
|
||||
* @param targetParentId The id of the folder-node where the node have to be moved to
|
||||
*/
|
||||
moveNode(nodeId: string, targetParentId: string) {
|
||||
return Observable.fromPromise(this.apiService.getInstance().nodes.moveNode(nodeId, { targetParentId }))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new folder in the path.
|
||||
* @param name Folder name
|
||||
* @param parentId Parent folder ID
|
||||
* @returns {any}
|
||||
*/
|
||||
createFolder(name: string, parentId: string): Observable<MinimalNodeEntity> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().nodes.createFolder(name, '/', parentId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder node with the specified relative name path below the root node.
|
||||
* @param folder Path to folder.
|
||||
* @param opts Options.
|
||||
* @returns {Observable<NodePaging>} Folder entity.
|
||||
*/
|
||||
getFolder(folder: string, opts?: any) {
|
||||
return Observable.fromPromise(this.getNodesPromise(folder, opts))
|
||||
.map(res => <NodePaging> res)
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
getFolderNode(nodeId: string): Promise<MinimalNodeEntryEntity> {
|
||||
let opts: any = {
|
||||
includeSource: true,
|
||||
include: ['path', 'properties', 'allowableOperations']
|
||||
};
|
||||
|
||||
let nodes: any = this.apiService.getInstance().nodes;
|
||||
return nodes.getNodeInfo(nodeId, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thumbnail URL for the given document node.
|
||||
* @param node Node to get URL for.
|
||||
* @returns {string} URL address.
|
||||
*/
|
||||
getDocumentThumbnailUrl(node: MinimalNodeEntity) {
|
||||
return this.thumbnailService.getDocumentThumbnailUrl(node);
|
||||
}
|
||||
|
||||
getMimeTypeIcon(mimeType: string): string {
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
getDefaultMimeTypeIcon(): string {
|
||||
return this.thumbnailService.getDefaultMimeTypeIcon();
|
||||
}
|
||||
|
||||
hasPermission(node: any, permission: PermissionsEnum|string): boolean {
|
||||
return this.contentService.hasPermission(node, permission);
|
||||
}
|
||||
|
||||
private handleError(error: Response) {
|
||||
// in a real world app, we may send the error to some remote logging infrastructure
|
||||
// instead of just logging it to the console
|
||||
this.logService.error(error);
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
}
|
@@ -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 { async, TestBed } from '@angular/core/testing';
|
||||
import { TranslationService, AppConfigService, NotificationService } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { FileNode, FolderNode } from '../../mock';
|
||||
import { ContentActionHandler } from '../models/content-action.model';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
import { FolderActionsService } from './folder-actions.service';
|
||||
import { NodeActionsService } from './node-actions.service';
|
||||
|
||||
describe('FolderActionsService', () => {
|
||||
|
||||
let service: FolderActionsService;
|
||||
let documentListService: DocumentListService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DocumentListService,
|
||||
FolderActionsService,
|
||||
NodeActionsService,
|
||||
TranslationService,
|
||||
NotificationService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
let appConfig: AppConfigService = TestBed.get(AppConfigService);
|
||||
appConfig.config.ecmHost = 'http://localhost:9876/ecm';
|
||||
|
||||
service = TestBed.get(FolderActionsService);
|
||||
documentListService = TestBed.get(DocumentListService);
|
||||
});
|
||||
|
||||
it('should register custom action handler', () => {
|
||||
let handler: ContentActionHandler = function () {
|
||||
};
|
||||
service.setHandler('<key>', handler);
|
||||
expect(service.getHandler('<key>')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should not find handler that is not registered', () => {
|
||||
expect(service.getHandler('<missing>')).toBeNull();
|
||||
});
|
||||
|
||||
it('should be case insensitive for keys', () => {
|
||||
let handler: ContentActionHandler = function () {
|
||||
};
|
||||
service.setHandler('<key>', handler);
|
||||
expect(service.getHandler('<KEY>')).toBe(handler);
|
||||
});
|
||||
|
||||
it('should not find handler with invalid key', () => {
|
||||
expect(service.getHandler(null)).toBeNull();
|
||||
expect(service.getHandler('')).toBeNull();
|
||||
});
|
||||
|
||||
it('should allow action execution only when service available', () => {
|
||||
let folder = new FolderNode();
|
||||
expect(service.canExecuteAction(folder)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow action execution only for folder nodes', () => {
|
||||
expect(service.canExecuteAction(null)).toBeFalsy();
|
||||
expect(service.canExecuteAction(new FileNode())).toBeFalsy();
|
||||
expect(service.canExecuteAction(new FolderNode())).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set new handler only by key', () => {
|
||||
let handler: ContentActionHandler = function () {
|
||||
};
|
||||
expect(service.setHandler(null, handler)).toBeFalsy();
|
||||
expect(service.setHandler('', handler)).toBeFalsy();
|
||||
expect(service.setHandler('my-handler', handler)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should register delete action', () => {
|
||||
expect(service.getHandler('delete')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not delete the folder node if there are no permissions', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callThrough();
|
||||
|
||||
service.permissionEvent.subscribe((permission) => {
|
||||
expect(permission).toBeDefined();
|
||||
expect(permission.type).toEqual('folder');
|
||||
expect(permission.action).toEqual('delete');
|
||||
done();
|
||||
});
|
||||
|
||||
let folder = new FolderNode();
|
||||
service.getHandler('delete')(folder);
|
||||
|
||||
});
|
||||
|
||||
it('should delete the folder node if there is the delete permission', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = [permission];
|
||||
const deleteObservale = service.getHandler('delete')(folderWithPermission, null, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
|
||||
expect(deleteObservale.subscribe).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not delete the folder node if there is no delete permission', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
service.permissionEvent.subscribe((permission) => {
|
||||
expect(permission).toBeDefined();
|
||||
expect(permission.type).toEqual('folder');
|
||||
expect(permission.action).toEqual('delete');
|
||||
done();
|
||||
});
|
||||
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = ['create', 'update'];
|
||||
service.getHandler('delete')(folderWithPermission);
|
||||
});
|
||||
|
||||
it('should call the error on the returned Observable if there is no delete permission', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = ['create', 'update'];
|
||||
const deleteObservable = service.getHandler('delete')(folderWithPermission);
|
||||
|
||||
deleteObservable.subscribe({
|
||||
error: (error) => {
|
||||
expect(error.message).toEqual('No permission to delete');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should delete the folder node if there is the delete and others permission ', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = ['create', 'update', permission];
|
||||
service.getHandler('delete')(folderWithPermission, null, permission);
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
|
||||
});
|
||||
|
||||
it('should support deletion only folder node', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let file = new FileNode();
|
||||
service.getHandler('delete')(file);
|
||||
expect(documentListService.deleteNode).not.toHaveBeenCalled();
|
||||
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = [permission];
|
||||
service.getHandler('delete')(folderWithPermission, null, permission);
|
||||
expect(documentListService.deleteNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require node id to delete', () => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let folder = new FolderNode();
|
||||
folder.entry.id = null;
|
||||
service.getHandler('delete')(folder);
|
||||
|
||||
expect(documentListService.deleteNode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reload target upon node deletion', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let target = jasmine.createSpyObj('obj', ['reload']);
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = [permission];
|
||||
|
||||
let deleteHandler = service.getHandler('delete')(folderWithPermission, target, permission);
|
||||
|
||||
deleteHandler.subscribe(() => {
|
||||
expect(target.reload).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
expect(documentListService.deleteNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit success event upon node deletion', (done) => {
|
||||
spyOn(documentListService, 'deleteNode').and.callFake(() => {
|
||||
return new Observable<any>(observer => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
service.success.subscribe((nodeId) => {
|
||||
expect(nodeId).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
let permission = 'delete';
|
||||
let target = jasmine.createSpyObj('obj', ['reload']);
|
||||
let folder = new FolderNode();
|
||||
let folderWithPermission: any = folder;
|
||||
folderWithPermission.entry.allowableOperations = [permission];
|
||||
|
||||
service.getHandler('delete')(folderWithPermission, target, permission);
|
||||
});
|
||||
});
|
@@ -0,0 +1,112 @@
|
||||
/*!
|
||||
* @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 { ContentService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
import { ContentActionHandler } from '../models/content-action.model';
|
||||
import { PermissionModel } from '../models/permissions.model';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
import { NodeActionsService } from './node-actions.service';
|
||||
|
||||
@Injectable()
|
||||
export class FolderActionsService {
|
||||
|
||||
permissionEvent: Subject<PermissionModel> = new Subject<PermissionModel>();
|
||||
error: Subject<Error> = new Subject<Error>();
|
||||
success: Subject<string> = new Subject<string>();
|
||||
|
||||
private handlers: { [id: string]: ContentActionHandler; } = {};
|
||||
|
||||
constructor(private nodeActionsService: NodeActionsService,
|
||||
private documentListService: DocumentListService,
|
||||
private contentService: ContentService) {
|
||||
this.setupActionHandlers();
|
||||
}
|
||||
|
||||
getHandler(key: string): ContentActionHandler {
|
||||
if (key) {
|
||||
let lkey = key.toLowerCase();
|
||||
return this.handlers[lkey] || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setHandler(key: string, handler: ContentActionHandler): boolean {
|
||||
if (key) {
|
||||
let lkey = key.toLowerCase();
|
||||
this.handlers[lkey] = handler;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canExecuteAction(obj: any): boolean {
|
||||
return this.documentListService && obj && obj.entry.isFolder === true;
|
||||
}
|
||||
|
||||
private setupActionHandlers() {
|
||||
this.handlers['copy'] = this.copyNode.bind(this);
|
||||
this.handlers['move'] = this.moveNode.bind(this);
|
||||
this.handlers['delete'] = this.deleteNode.bind(this);
|
||||
}
|
||||
|
||||
private copyNode(obj: MinimalNodeEntity, target?: any, permission?: string) {
|
||||
const actionObservable = this.nodeActionsService.copyFolder(obj.entry, permission);
|
||||
this.prepareHandlers(actionObservable, 'folder', 'copy', target, permission);
|
||||
return actionObservable;
|
||||
}
|
||||
|
||||
private moveNode(obj: MinimalNodeEntity, target?: any, permission?: string) {
|
||||
const actionObservable = this.nodeActionsService.moveFolder(obj.entry, permission);
|
||||
this.prepareHandlers(actionObservable, 'folder', 'move', target, permission);
|
||||
return actionObservable;
|
||||
}
|
||||
|
||||
private prepareHandlers(actionObservable, type: string, action: string, target?: any, permission?: string): void {
|
||||
actionObservable.subscribe(
|
||||
(fileOperationMessage) => {
|
||||
if (target && typeof target.reload === 'function') {
|
||||
target.reload();
|
||||
}
|
||||
this.success.next(fileOperationMessage);
|
||||
},
|
||||
this.error.next.bind(this.error)
|
||||
);
|
||||
}
|
||||
|
||||
private deleteNode(obj: any, target?: any, permission?: string): Observable<any> {
|
||||
let handlerObservable: Observable<any>;
|
||||
|
||||
if (this.canExecuteAction(obj)) {
|
||||
if (this.contentService.hasPermission(obj.entry, permission)) {
|
||||
handlerObservable = this.documentListService.deleteNode(obj.entry.id);
|
||||
handlerObservable.subscribe(() => {
|
||||
if (target && typeof target.reload === 'function') {
|
||||
target.reload();
|
||||
}
|
||||
this.success.next(obj.entry.id);
|
||||
});
|
||||
return handlerObservable;
|
||||
} else {
|
||||
this.permissionEvent.next(new PermissionModel({type: 'folder', action: 'delete', permission: permission}));
|
||||
return Observable.throw(new Error('No permission to delete'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
/*!
|
||||
* @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 { DataColumn } from '@alfresco/core';
|
||||
import { ContentService } from '@alfresco/core';
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { Subject } from 'rxjs/Rx';
|
||||
import { ContentNodeSelectorComponent, ContentNodeSelectorComponentData } from '../../content-node-selector/content-node-selector.component';
|
||||
import { ShareDataRow } from '../data/share-data-row.model';
|
||||
import { DocumentListService } from './document-list.service';
|
||||
|
||||
@Injectable()
|
||||
export class NodeActionsService {
|
||||
|
||||
constructor(private dialog: MatDialog,
|
||||
private documentListService?: DocumentListService,
|
||||
private contentService?: ContentService) {}
|
||||
|
||||
/**
|
||||
* Copy content node
|
||||
*
|
||||
* @param contentEntry node to copy
|
||||
* @param permission permission which is needed to apply the action
|
||||
*/
|
||||
public copyContent(contentEntry: MinimalNodeEntryEntity, permission?: string): Subject<string> {
|
||||
return this.doFileOperation('copy', 'content', contentEntry, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy folder node
|
||||
*
|
||||
* @param contentEntry node to copy
|
||||
* @param permission permission which is needed to apply the action
|
||||
*/
|
||||
public copyFolder(contentEntry: MinimalNodeEntryEntity, permission?: string): Subject<string> {
|
||||
return this.doFileOperation('copy', 'folder', contentEntry, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move content node
|
||||
*
|
||||
* @param contentEntry node to move
|
||||
* @param permission permission which is needed to apply the action
|
||||
*/
|
||||
public moveContent(contentEntry: MinimalNodeEntryEntity, permission?: string): Subject<string> {
|
||||
return this.doFileOperation('move', 'content', contentEntry, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move folder node
|
||||
*
|
||||
* @param contentEntry node to move
|
||||
* @param permission permission which is needed to apply the action
|
||||
*/
|
||||
public moveFolder(contentEntry: MinimalNodeEntryEntity, permission?: string): Subject<string> {
|
||||
return this.doFileOperation('move', 'folder', contentEntry, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* General method for performing the given operation (copy|move)
|
||||
*
|
||||
* @param action the action to perform (copy|move)
|
||||
* @param type type of the content (content|folder)
|
||||
* @param contentEntry the contentEntry which has to have the action performed on
|
||||
* @param permission permission which is needed to apply the action
|
||||
*/
|
||||
private doFileOperation(action: string, type: string, contentEntry: MinimalNodeEntryEntity, permission?: string): Subject<string> {
|
||||
const observable: Subject<string> = new Subject<string>();
|
||||
|
||||
if (this.contentService.hasPermission(contentEntry, permission)) {
|
||||
const data: ContentNodeSelectorComponentData = {
|
||||
title: `${action} ${contentEntry.name} to ...`,
|
||||
currentFolderId: contentEntry.parentId,
|
||||
rowFilter: this.rowFilter.bind(this, contentEntry.id),
|
||||
imageResolver: this.imageResolver.bind(this),
|
||||
select: new EventEmitter<MinimalNodeEntryEntity[]>()
|
||||
};
|
||||
|
||||
this.dialog.open(ContentNodeSelectorComponent, { data, panelClass: 'adf-content-node-selector-dialog', width: '630px' });
|
||||
|
||||
data.select.subscribe((selections: MinimalNodeEntryEntity[]) => {
|
||||
const selection = selections[0];
|
||||
this.documentListService[`${action}Node`].call(this.documentListService, contentEntry.id, selection.id)
|
||||
.subscribe(
|
||||
observable.next.bind(observable, `OPERATION.SUCCES.${type.toUpperCase()}.${action.toUpperCase()}`),
|
||||
observable.error.bind(observable)
|
||||
);
|
||||
this.dialog.closeAll();
|
||||
});
|
||||
|
||||
return observable;
|
||||
} else {
|
||||
observable.error(new Error(JSON.stringify({ error: { statusCode: 403 } })));
|
||||
return observable;
|
||||
}
|
||||
}
|
||||
|
||||
private rowFilter(currentNodeId, row: ShareDataRow): boolean {
|
||||
const node: MinimalNodeEntryEntity = row.node.entry;
|
||||
|
||||
if (node.id === currentNodeId || node.isFile) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private imageResolver(row: ShareDataRow, col: DataColumn): string|null {
|
||||
const entry: MinimalNodeEntryEntity = row.node.entry;
|
||||
if (!this.contentService.hasPermission(entry, 'create')) {
|
||||
return this.documentListService.getMimeTypeIcon('disable/folder');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user