[ACA-1432] unified selection and single info drawer (#385)

* track document list selection state

* selection management enhancements

* (fix) hide info drawer on selection reset

* use store selection

* remove event handler

* upgrade info drawer for personal files

* upgrade favorties

* upgrade recent files

* move info drawer to a separate component

* test fixes

* update tests

* test fixes

* remove obsolete directive

* use last selection entry

* switch back to first selected node

* selection improvements, versioning uses same node

* optimised toolbar visibility evaluation

* upgrade libs

* update js api

* test fixes

* test fixes

* test updates

* test fixes

* fix e2e tests

* show metadata for last clicked node
This commit is contained in:
Denys Vuika 2018-06-06 12:44:13 +01:00 committed by GitHub
parent a67dd43ad6
commit f0c0fe162b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 512 additions and 671 deletions

View File

@ -135,11 +135,6 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1, file2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`);
@ -154,11 +149,6 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple folders are selected', () => {
dataTable.selectMultipleItems([folder1, folder2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`);
@ -171,13 +161,39 @@ describe('Toolbar actions - multiple selection : ', () => {
.then(() => dataTable.clearSelection());
});
it('should display View action when at least one file selected', async () => {
await dataTable.selectMultipleItems([folder1, file1, folder2]);
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'Action is not displayed');
});
it('should not display View action when only folders selected', async () => {
await dataTable.selectMultipleItems([folder1, folder2]);
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'Action is displayed');
});
it('should display Download action for selected items', async () => {
await dataTable.selectMultipleItems([folder1, file1, folder2]);
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Action is not displayed');
});
it('should not display Download action for empty selection', async () => {
await dataTable.selectMultipleItems([folder1, file1, folder2]);
await dataTable.clearSelection();
expect(toolbar.actions.isButtonPresent('Download')).toBe(false, 'Action is displayed');
});
it('should display Edit action when at least one folder selected', async () => {
await dataTable.selectMultipleItems([folder1, file1, folder2]);
expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Action is not displayed');
});
it('should not display Edit action if no folders selected', async () => {
await dataTable.selectMultipleItems([file1, file2]);
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Action is displayed');
});
it('correct actions appear when both files and folders are selected', () => {
dataTable.selectMultipleItems([file1, file2, folder1, folder2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`);
@ -228,7 +244,7 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1Admin, file2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
@ -247,9 +263,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple folders are selected', () => {
dataTable.selectMultipleItems([folder1Admin, folder2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Edit is not displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -266,9 +282,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when both files and folders are selected', () => {
dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Edit is not displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -295,7 +311,7 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1Admin, file2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
@ -314,16 +330,16 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple folders are selected', () => {
dataTable.selectMultipleItems([folder1Admin, folder2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`);
expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`);
expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`);
expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`);
expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed`);
expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed`);
expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed`);
expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed`);
})
// .then(() => browser.$('body').click())
.then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform())
@ -333,9 +349,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when both files and folders are selected', () => {
dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -369,7 +385,7 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1, file2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
})
@ -404,9 +420,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1, file2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -439,9 +455,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple files are selected', () => {
dataTable.selectMultipleItems([file1, file2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -458,9 +474,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when multiple folders are selected', () => {
dataTable.selectMultipleItems([folder1, folder2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Edit is not displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {
@ -477,9 +493,9 @@ describe('Toolbar actions - multiple selection : ', () => {
it('correct actions appear when both files and folders are selected', () => {
dataTable.selectMultipleItems([file1, file2, folder1, folder2])
.then(() => {
expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files');
expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'View is not displayed');
expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files');
expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Edit is not displayed');
})
.then(() => toolbar.actions.openMoreMenu())
.then(menu => {

48
package-lock.json generated
View File

@ -5,11 +5,11 @@
"requires": true,
"dependencies": {
"@alfresco/adf-content-services": {
"version": "2.4.0-beta10",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.4.0-beta10.tgz",
"integrity": "sha512-uBHkDrp60oeen770MjCh8aCFZm/g8nOIdilruB26EXaj9KOE3ZJ2ptGrHVGf51qrWRyDjnne/dwKvHW2O+kxvA==",
"version": "2.4.0-24b573b08f3a072327efefc0196bd949cbca3766",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.4.0-24b573b08f3a072327efefc0196bd949cbca3766.tgz",
"integrity": "sha512-5B/JHEsCdvksEP16TyS5yHtOT/zCOLj7uHS8lqGhu7Z20Cj5K7RTBwco4flTh3T8OSyoUBzdmp3HQgfqJk/QrQ==",
"requires": {
"@alfresco/adf-core": "2.4.0-beta10",
"@alfresco/adf-core": "2.4.0-24b573b08f3a072327efefc0196bd949cbca3766",
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
"@angular/common": "5.1.1",
@ -24,7 +24,7 @@
"@angular/platform-browser-dynamic": "5.1.1",
"@angular/router": "5.1.1",
"@ngx-translate/core": "9.1.1",
"alfresco-js-api": "2.4.0-beta9",
"alfresco-js-api": "2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2",
"chart.js": "2.5.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
@ -40,15 +40,6 @@
"zone.js": "0.8.14"
},
"dependencies": {
"alfresco-js-api": {
"version": "2.4.0-beta9",
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-beta9.tgz",
"integrity": "sha512-4SlaFerEucx+Gnusf4dwhEfrCvwWTKBxJm1kwe7eoKnPJO0I6B/Zx1I/Dkvvw4GVqO2f5WYoab+XGQSdMNyOng==",
"requires": {
"event-emitter": "0.3.4",
"superagent": "3.8.2"
}
},
"core-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
@ -70,9 +61,9 @@
}
},
"@alfresco/adf-core": {
"version": "2.4.0-beta10",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.4.0-beta10.tgz",
"integrity": "sha512-w8W6Uv9QfLOWojF+2zpvnjlYsAhJErawc77rIFPROQIN81nibSQ8Y6Xm3NdsrCz2drlrKbx5XD8yzia+2QKeyw==",
"version": "2.4.0-24b573b08f3a072327efefc0196bd949cbca3766",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.4.0-24b573b08f3a072327efefc0196bd949cbca3766.tgz",
"integrity": "sha512-vDC4Wz+j6uSEMzdZUhy5xB0Sm63brb360o+uiPXs0uMyL9agMEh5wUPAl4Zfic0alnlww8OGADA8r6sFwb7HhQ==",
"requires": {
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
@ -88,7 +79,7 @@
"@angular/platform-browser-dynamic": "5.1.1",
"@angular/router": "5.1.1",
"@ngx-translate/core": "9.1.1",
"alfresco-js-api": "2.4.0-beta9",
"alfresco-js-api": "2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2",
"chart.js": "2.5.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
@ -104,15 +95,6 @@
"zone.js": "0.8.14"
},
"dependencies": {
"alfresco-js-api": {
"version": "2.4.0-beta9",
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-beta9.tgz",
"integrity": "sha512-4SlaFerEucx+Gnusf4dwhEfrCvwWTKBxJm1kwe7eoKnPJO0I6B/Zx1I/Dkvvw4GVqO2f5WYoab+XGQSdMNyOng==",
"requires": {
"event-emitter": "0.3.4",
"superagent": "3.8.2"
}
},
"core-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
@ -674,9 +656,9 @@
"integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo="
},
"alfresco-js-api": {
"version": "2.4.0-beta9",
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-beta9.tgz",
"integrity": "sha512-4SlaFerEucx+Gnusf4dwhEfrCvwWTKBxJm1kwe7eoKnPJO0I6B/Zx1I/Dkvvw4GVqO2f5WYoab+XGQSdMNyOng==",
"version": "2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2",
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2.tgz",
"integrity": "sha512-HWA2zLbuRTi1mjIoaEW2ZSWKMDfOWcYOgplFZovQgXUOaFZuPJH1AQ7RbImq/G5/NKwe9AJSUAnkg5vpuKEBTg==",
"requires": {
"event-emitter": "0.3.4",
"superagent": "3.8.2"
@ -2400,9 +2382,9 @@
"dev": true
},
"cookiejar": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz",
"integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
"integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA=="
},
"copy-concurrently": {
"version": "1.0.5",

View File

@ -25,8 +25,8 @@
},
"private": true,
"dependencies": {
"@alfresco/adf-content-services": "2.4.0-beta10",
"@alfresco/adf-core": "2.4.0-beta10",
"@alfresco/adf-content-services": "2.4.0-24b573b08f3a072327efefc0196bd949cbca3766",
"@alfresco/adf-core": "2.4.0-24b573b08f3a072327efefc0196bd949cbca3766",
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
"@angular/common": "5.1.1",
@ -48,7 +48,7 @@
"@ngrx/store-devtools": "^5.2.0",
"@ngstack/electron": "0.1.0",
"@ngx-translate/core": "9.1.1",
"alfresco-js-api": "2.4.0-beta9",
"alfresco-js-api": "2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2",
"core-js": "2.5.3",
"cspell": "^2.1.12",
"hammerjs": "2.0.8",

View File

@ -62,7 +62,6 @@ import { NodeMoveDirective } from './common/directives/node-move.directive';
import { NodeRestoreDirective } from './common/directives/node-restore.directive';
import { NodePermanentDeleteDirective } from './common/directives/node-permanent-delete.directive';
import { NodeUnshareDirective } from './common/directives/node-unshare.directive';
import { NodeInfoDirective } from './common/directives/node-info.directive';
import { NodeVersionsDirective } from './common/directives/node-versions.directive';
import { VersionManagerDialogAdapterComponent } from './components/versions-dialog/version-manager-dialog-adapter.component';
import { BrowsingFilesService } from './common/services/browsing-files.service';
@ -77,6 +76,7 @@ import { SortingPreferenceKeyDirective } from './directives/sorting-preference-k
import { INITIAL_STATE } from './store/states/app.state';
import { appReducer } from './store/reducers/app.reducer';
import { InfoDrawerComponent } from './components/info-drawer/info-drawer.component';
@NgModule({
@ -127,12 +127,12 @@ import { appReducer } from './store/reducers/app.reducer';
NodeRestoreDirective,
NodePermanentDeleteDirective,
NodeUnshareDirective,
NodeInfoDirective,
NodeVersionsDirective,
VersionManagerDialogAdapterComponent,
SearchComponent,
SettingsComponent,
SortingPreferenceKeyDirective
SortingPreferenceKeyDirective,
InfoDrawerComponent
],
providers: [
{ provide: AppConfigService, useClass: HybridAppConfigService },

View File

@ -1,121 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component } from '@angular/core';
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { AlfrescoApiService, CoreModule } from '@alfresco/adf-core';
import { NodeInfoDirective } from './node-info.directive';
@Component({
template: '<div [acaNodeInfo]="selection"></div>'
})
class TestComponent {
selection;
}
describe('NodeInfoDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
let apiService: AlfrescoApiService;
let nodeService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule
],
declarations: [
TestComponent,
NodeInfoDirective
]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
apiService = TestBed.get(AlfrescoApiService);
}));
beforeEach(() => {
nodeService = apiService.getInstance().nodes;
spyOn(nodeService, 'getNodeInfo').and.returnValue(Promise.resolve({
entry: { name: 'borg' }
}));
});
it('should not get node info onInit when selection is empty', () => {
component.selection = [];
fixture.detectChanges();
expect(nodeService.getNodeInfo).not.toHaveBeenCalled();
});
it('should get node info onInit when selection is not empty', () => {
component.selection = [{ entry: { id: 'id' } }];
fixture.detectChanges();
expect(nodeService.getNodeInfo).toHaveBeenCalled();
});
it('should not get node info on event when selection is empty', () => {
component.selection = [];
fixture.detectChanges();
document.dispatchEvent(new CustomEvent('click'));
expect(nodeService.getNodeInfo).not.toHaveBeenCalled();
});
it('should get node info on event from selection', () => {
component.selection = [{ entry: { id: 'id' } }];
fixture.detectChanges();
document.dispatchEvent(new CustomEvent('click'));
expect(nodeService.getNodeInfo).toHaveBeenCalledWith('id', { include: [ 'allowableOperations' ] });
});
it('should get node info of last entry when selecting multiple nodes', fakeAsync(() => {
component.selection = [
{ entry: { id: 'id1', isFile: true } },
{ entry: { id: 'id2', isFile: true } },
{ entry: { id: 'id3', isFile: true } }
];
fixture.detectChanges();
document.dispatchEvent(new CustomEvent('click'));
fixture.detectChanges();
tick();
expect(nodeService.getNodeInfo).toHaveBeenCalledWith('id3', { include: [ 'allowableOperations' ] });
}));
});

View File

@ -1,83 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Directive, HostListener, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
@Directive({
selector: '[acaNodeInfo]',
exportAs: 'nodeInfo'
})
export class NodeInfoDirective implements OnInit {
// tslint:disable-next-line:no-input-rename
@Input('acaNodeInfo') selection: MinimalNodeEntity[];
@Output() changed: EventEmitter<null|MinimalNodeEntryEntity> = new EventEmitter<null|MinimalNodeEntryEntity>();
@Output() error: EventEmitter<null> = new EventEmitter<null>();
node: MinimalNodeEntryEntity;
loading: boolean = null;
@HostListener('document:node-click', ['$event'])
onClick(event) {
this.getNodeInfo();
}
constructor(private apiService: AlfrescoApiService) {}
ngOnInit() {
this.getNodeInfo();
}
getNodeInfo() {
if (!this.selection.length) {
this.node = null;
this.loading = false;
this.changed.emit(null);
return;
}
const node = this.selection[this.selection.length - 1];
if (node) {
this.loading = true;
this.apiService.getInstance().nodes
.getNodeInfo((<any>node.entry).nodeId || node.entry.id, {
include: ['allowableOperations']
})
.then((data: MinimalNodeEntryEntity) => {
this.node = data;
this.changed.emit(data);
this.loading = false;
})
.catch(() => {
this.error.emit();
this.loading = false;
});
}
}
}

View File

@ -31,6 +31,7 @@ import { AlfrescoApiService, TranslationService, NotificationService, CoreModule
import { NodePermanentDeleteDirective } from './node-permanent-delete.directive';
import { MatDialogModule, MatDialog } from '@angular/material';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
template: `<div [app-permanent-delete-node]="selection"></div>`
@ -53,6 +54,7 @@ describe('NodePermanentDeleteDirective', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
CoreModule,
MatDialogModule
],
@ -69,7 +71,10 @@ describe('NodePermanentDeleteDirective', () => {
directiveInstance = element.injector.get(NodePermanentDeleteDirective);
dialog = TestBed.get(MatDialog);
alfrescoService = TestBed.get(AlfrescoApiService);
alfrescoService.reset();
translation = TestBed.get(TranslationService);
notificationService = TestBed.get(NotificationService);
});

View File

@ -33,7 +33,7 @@ import { Observable } from 'rxjs/Rx';
import { AlfrescoApiService, TranslationService, NotificationService, CoreModule } from '@alfresco/adf-core';
import { NodeRestoreDirective } from './node-restore.directive';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
template: `<div [acaRestoreNode]="selection"></div>`
@ -57,7 +57,7 @@ describe('NodeRestoreDirective', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
NoopAnimationsModule,
RouterTestingModule,
CoreModule
],
@ -74,6 +74,8 @@ describe('NodeRestoreDirective', () => {
directiveInstance = element.injector.get(NodeRestoreDirective);
alfrescoService = TestBed.get(AlfrescoApiService);
alfrescoService.reset();
translation = TestBed.get(TranslationService);
notificationService = TestBed.get(NotificationService);
router = TestBed.get(Router);

View File

@ -26,7 +26,7 @@
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { TranslationService, NotificationService, AlfrescoApiService } from '@alfresco/adf-core';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { VersionManagerDialogAdapterComponent } from '../../components/versions-dialog/version-manager-dialog-adapter.component';
import { MatDialog } from '@angular/material';
@ -38,7 +38,7 @@ export class NodeVersionsDirective {
// tslint:disable-next-line:no-input-rename
@Input('acaNodeVersions')
selection: MinimalNodeEntity[];
node: MinimalNodeEntity;
@Output()
nodeVersionError: EventEmitter<any> = new EventEmitter();
@ -55,17 +55,23 @@ export class NodeVersionsDirective {
private translation: TranslationService
) {}
onManageVersions() {
const contentEntry = this.selection[0].entry;
const nodeId = (<any>contentEntry).nodeId;
this.apiService.getInstance().nodes.getNodeInfo(nodeId || contentEntry.id, {
include: ['allowableOperations']
}).then(entry => this.openVersionManagerDialog(entry));
async onManageVersions() {
if (this.node && this.node.entry) {
let entry = this.node.entry;
if (entry.nodeId) {
entry = await this.apiService.nodesApi.getNodeInfo(
entry.nodeId,
{ include: ['allowableOperations'] }
);
this.openVersionManagerDialog(entry);
} else {
this.openVersionManagerDialog(entry);
}
}
}
openVersionManagerDialog(contentEntry) {
openVersionManagerDialog(contentEntry: MinimalNodeEntryEntity) {
if (contentEntry.isFile) {
this.dialog.open(
VersionManagerDialogAdapterComponent,

View File

@ -3,39 +3,37 @@
<adf-breadcrumb root="APP.BROWSE.FAVORITES.TITLE">
</adf-breadcrumb>
<adf-toolbar class="inline">
<adf-toolbar class="inline" *ngIf="hasSelection">
<button
mat-icon-button
color="primary"
*ngIf="isFileSelected(documentList.selection)"
*ngIf="firstSelectedDocument"
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
(click)="onNodeDoubleClick(documentList.selection[0]?.entry)">
(click)="showPreview(firstSelectedDocument)">
<mat-icon>open_in_browser</mat-icon>
</button>
<button
mat-icon-button
color="primary"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[adfNodeDownload]="documentList.selection">
[adfNodeDownload]="selectedNodes">
<mat-icon>get_app</mat-icon>
</button>
<button
mat-icon-button
color="primary"
*ngIf="showEditOption(documentList.selection)"
*ngIf="firstSelectedFolder"
[attr.title]="'APP.ACTIONS.EDIT' | translate"
(error)="openSnackMessage($event)"
[adf-edit-folder]="documentList.selection[0]?.entry">
[adf-edit-folder]="firstSelectedFolder?.entry">
<mat-icon>create</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'accent' : 'primary'"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
@ -44,7 +42,6 @@
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.MORE' | translate }}"
[matMenuTriggerFor]="actionsMenu">
<mat-icon>more_vert</mat-icon>
@ -55,7 +52,7 @@
mat-menu-item
#selection="adfFavorite"
(toggle)="reload()"
[adf-node-favorite]="documentList.selection">
[adf-node-favorite]="selectedNodes">
<mat-icon color="primary" *ngIf="selection.hasFavorites()">star</mat-icon>
<mat-icon *ngIf="!selection.hasFavorites()">star_border</mat-icon>
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
@ -63,29 +60,29 @@
<button
mat-menu-item
[acaCopyNode]="documentList.selection">
[acaCopyNode]="selectedNodes">
<mat-icon>content_copy</mat-icon>
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
</button>
<button
mat-menu-item
[acaMoveNode]="documentList.selection">
[acaMoveNode]="selectedNodes">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
</button>
<button
mat-menu-item
[acaDeleteNode]="documentList.selection">
[acaDeleteNode]="selectedNodes">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="isFileSelected(documentList.selection)"
[acaNodeVersions]="documentList.selection">
*ngIf="firstSelectedDocument"
[acaNodeVersions]="firstSelectedDocument">
<mat-icon>history</mat-icon>
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
</button>
@ -100,7 +97,10 @@
[navigate]="false"
[sorting]="[ 'modifiedAt', 'desc' ]"
[acaSortingPreferenceKey]="sortingPreferenceKey"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)">
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)"
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)">
<empty-folder-content>
<ng-template>
@ -168,39 +168,8 @@
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[acaNodeInfo]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE' | translate">
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.PROPERTIES' | translate">
<div *ngIf="infoInstance.loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<adf-content-metadata-card
[readOnly]="!permission.check(infoInstance.node, ['update'])"
[displayEmpty]="permission.check(infoInstance.node, ['update'])"
[preset]="'custom'"
[node]="infoInstance.node">
</adf-content-metadata-card>
</adf-info-drawer-tab>
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.VERSIONS' | translate">
<ng-container *ngIf="infoInstance.node?.isFile;else choose_document_template">
<adf-version-manager [node]="infoInstance.node"></adf-version-manager>
</ng-container>
<ng-template #choose_document_template>
<div class="adf-manage-versions-empty">
<mat-icon class="adf-manage-versions-empty-icon">face</mat-icon>
{{ 'VERSION.SELECTION.EMPTY' | translate }}
</div>
</ng-template>
</adf-info-drawer-tab>
</adf-info-drawer>
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
<aca-info-drawer [node]="lastSelectedNode"></aca-info-drawer>
</div>
</div>
</div>

View File

@ -44,10 +44,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { FavoritesComponent } from './favorites.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('Favorites Routed Component', () => {
let fixture: ComponentFixture<FavoritesComponent>;
@ -95,14 +97,14 @@ describe('Favorites Routed Component', () => {
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
MatSnackBarModule, MatIconModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
FavoritesComponent,
AppConfigPipe
@ -232,56 +234,6 @@ describe('Favorites Routed Component', () => {
});
});
describe('edit option', () => {
it('should return false if a file node is selected', () => {
const selection = [
{
entry: {
isFolder: false,
isFile: true
}
}
];
const result = component.showEditOption(selection);
expect(result).toBe(false);
});
it('should return false if multiple nodes are selected', () => {
const selection = [
{
entry: {
isFolder: true,
isFile: false
}
},
{
entry: {
isFolder: true,
isFile: false
}
}
];
const result = component.showEditOption(selection);
expect(result).toBe(false);
});
it('should return true if selected node is a folder', () => {
const selection = [
{
entry: {
isFolder: true,
isFile: false
}
}
];
const result = component.showEditOption(selection);
expect(result).toBe(true);
});
});
describe('refresh', () => {
it('should call document list reload', () => {
spyOn(component.documentList, 'reload');

View File

@ -25,30 +25,35 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { MinimalNodeEntryEntity, MinimalNodeEntity, PathElementEntity, PathInfo } from 'alfresco-js-api';
import { MinimalNodeEntryEntity, PathElementEntity, PathInfo } from 'alfresco-js-api';
import { ContentService, NodesApiService, UserPreferencesService, NotificationService } from '@alfresco/adf-core';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './favorites.component.html'
})
export class FavoritesComponent extends PageComponent implements OnInit {
constructor(private router: Router,
constructor(router: Router,
route: ActivatedRoute,
store: Store<AcaState>,
private nodesApi: NodesApiService,
private contentService: ContentService,
private content: ContentManagementService,
private notificationService: NotificationService,
public permission: NodePermissionService,
preferences: UserPreferencesService) {
super(preferences, route);
super(preferences, router, route, store);
}
ngOnInit() {
super.ngOnInit();
this.subscriptions = this.subscriptions.concat([
this.content.nodeDeleted.subscribe(() => this.reload()),
this.content.nodeRestored.subscribe(() => this.reload()),
@ -87,10 +92,6 @@ export class FavoritesComponent extends PageComponent implements OnInit {
}
}
showEditOption(selection: MinimalNodeEntity[]) {
return selection && selection.length === 1 && selection[0].entry.isFolder;
}
openSnackMessage(event: any) {
this.notificationService.openSnackMessage(
event,

View File

@ -6,39 +6,36 @@
(navigate)="onBreadcrumbNavigate($event)">
</adf-breadcrumb>
<adf-toolbar class="inline">
<adf-toolbar class="inline" *ngIf="hasSelection">
<button
color="primary"
mat-icon-button
*ngIf="isFileSelected(documentList.selection)"
*ngIf="firstSelectedDocument"
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
(click)="showPreview(documentList.selection[0]?.entry)">
(click)="showPreview(firstSelectedDocument)">
<mat-icon>open_in_browser</mat-icon>
</button>
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[adfNodeDownload]="documentList.selection">
[adfNodeDownload]="selectedNodes">
<mat-icon>get_app</mat-icon>
</button>
<button
color="primary"
mat-icon-button
*ngIf="isFolderSelected(documentList.selection)
&& permission.check(documentList.selection, ['update'])"
*ngIf="firstSelectedFolder && permission.check(firstSelectedFolder, ['update'])"
[attr.title]="'APP.ACTIONS.EDIT' | translate"
(error)="openSnackMessage($event)"
[adf-edit-folder]="documentList.selection[0]?.entry">
[adf-edit-folder]="firstSelectedFolder?.entry">
<mat-icon>create</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'accent' : 'primary'"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
@ -47,7 +44,6 @@
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.MORE' | translate }}"
[matMenuTriggerFor]="actionsMenu">
<mat-icon>more_vert</mat-icon>
@ -58,7 +54,7 @@
<button
mat-menu-item
#selection="adfFavorite"
[adf-node-favorite]="documentList.selection">
[adf-node-favorite]="selectedNodes">
<mat-icon color="primary" *ngIf="selection.hasFavorites()">star</mat-icon>
<mat-icon *ngIf="!selection.hasFavorites()">star_border</mat-icon>
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
@ -66,31 +62,31 @@
<button
mat-menu-item
[acaCopyNode]="documentList.selection">
[acaCopyNode]="selectedNodes">
<mat-icon>content_copy</mat-icon>
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'])"
[acaMoveNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'])"
[acaMoveNode]="selectedNodes">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'])"
[acaDeleteNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'])"
[acaDeleteNode]="selectedNodes">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="isFileSelected(documentList.selection)"
[acaNodeVersions]="documentList.selection">
*ngIf="firstSelectedDocument"
[acaNodeVersions]="firstSelectedDocument">
<mat-icon>history</mat-icon>
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
</button>
@ -117,7 +113,9 @@
[navigate]="false"
[imageResolver]="imageResolver"
(node-dblclick)="onNodeDoubleClick($event)"
(node-select)="onNodeSelect($event, documentList)">
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)">
<data-columns>
<data-column
@ -168,41 +166,8 @@
</adf-upload-drag-area>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[acaNodeInfo]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE' | translate">
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.PROPERTIES' | translate">
<div *ngIf="infoInstance.loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<adf-content-metadata-card
[readOnly]="!permission.check(infoInstance.node, ['update'])"
[displayEmpty]="permission.check(infoInstance.node, ['update'])"
[preset]="'custom'"
[node]="infoInstance.node">
</adf-content-metadata-card>
</adf-info-drawer-tab>
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.VERSIONS' | translate">
<ng-container *ngIf="isFileSelected(documentList.selection);else choose_document_template">
<adf-version-manager
*ngIf="infoInstance.node"
[node]="infoInstance.node">
</adf-version-manager>
</ng-container>
<ng-template #choose_document_template>
<div class="adf-manage-versions-empty">
<mat-icon class="adf-manage-versions-empty-icon">face</mat-icon>
{{ 'VERSION.SELECTION.EMPTY' | translate }}
</div>
</ng-template>
</adf-info-drawer-tab>
</adf-info-drawer>
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
<aca-info-drawer [node]="lastSelectedNode"></aca-info-drawer>
</div>
</div>
</div>

View File

@ -46,9 +46,11 @@ import { ContentManagementService } from '../../common/services/content-manageme
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
import { NodeActionsService } from '../../common/services/node-actions.service';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { FilesComponent } from './files.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('FilesComponent', () => {
let node;
@ -73,7 +75,8 @@ describe('FilesComponent', () => {
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule,
MatDialogModule
MatDialogModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
FilesComponent,
@ -81,7 +84,6 @@ describe('FilesComponent', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
FileSizePipe,
AppConfigPipe

View File

@ -38,6 +38,8 @@ import { NodeActionsService } from '../../common/services/node-actions.service';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './files.component.html'
@ -48,8 +50,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private nodePath: PathElement[];
constructor(private router: Router,
constructor(router: Router,
route: ActivatedRoute,
store: Store<AcaState>,
private nodesApi: NodesApiService,
private nodeActionsService: NodeActionsService,
private uploadService: UploadService,
@ -60,10 +63,12 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private notificationService: NotificationService,
public permission: NodePermissionService,
preferences: UserPreferencesService) {
super(preferences, route);
super(preferences, router, route, store);
}
ngOnInit() {
super.ngOnInit();
const { route, contentManagementService, contentService, nodeActionsService, uploadService } = this;
const { data } = route.snapshot;
@ -156,14 +161,6 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
}
}
showPreview(node: MinimalNodeEntryEntity) {
if (node) {
if (node.isFile) {
this.router.navigate(['./preview', node.id], { relativeTo: this.route });
}
}
}
onBreadcrumbNavigate(route: PathElementEntity) {
// todo: review this approach once 5.2.3 is out
if (this.nodePath && this.nodePath.length > 2) {

View File

@ -50,7 +50,7 @@ describe('HeaderComponent', () => {
HttpClientModule,
RouterTestingModule,
TranslateModule.forRoot(),
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }),
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
HeaderComponent

View File

@ -0,0 +1,28 @@
<div *ngIf="isLoading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<ng-container *ngIf="node">
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE' | translate">
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.PROPERTIES' | translate">
<adf-content-metadata-card
[readOnly]="!canUpdateNode()"
[displayEmpty]="canUpdateNode()"
[preset]="'custom'"
[node]="displayNode">
</adf-content-metadata-card>
</adf-info-drawer-tab>
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.VERSIONS' | translate">
<ng-container *ngIf="isFileSelected;else empty">
<adf-version-manager [node]="displayNode"></adf-version-manager>
</ng-container>
<ng-template #empty>
<div class="adf-manage-versions-empty">
<mat-icon class="adf-manage-versions-empty-icon">face</mat-icon>
{{ 'VERSION.SELECTION.EMPTY' | translate }}
</div>
</ng-template>
</adf-info-drawer-tab>
</adf-info-drawer>
</ng-container>

View File

@ -0,0 +1,94 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { AlfrescoApiService } from '@alfresco/adf-core';
@Component({
selector: 'aca-info-drawer',
templateUrl: './info-drawer.component.html'
})
export class InfoDrawerComponent implements OnChanges {
@Input() nodeId: string;
@Input() node: MinimalNodeEntity;
isLoading = false;
displayNode: MinimalNodeEntryEntity;
canUpdateNode(): boolean {
if (this.node) {
if ((<any>this.node.entry).nodeId) {
return this.permission.check(this.node.entry, ['update'], {
target: 'allowableOperationsOnTarget'
});
}
return this.permission.check(this.node.entry, ['update']);
}
return false;
}
get isFileSelected(): boolean {
if (this.node && this.node.entry) {
return this.node.entry.isFile;
}
return false;
}
constructor(
public permission: NodePermissionService,
private apiService: AlfrescoApiService
) {}
ngOnChanges(changes: SimpleChanges) {
if (this.node) {
const entry = this.node.entry;
if (entry.nodeId) {
this.loadNodeInfo(entry.nodeId);
} else {
this.displayNode = this.node.entry;
}
}
}
private loadNodeInfo(nodeId: string) {
if (nodeId) {
this.isLoading = true;
this.apiService.nodesApi
.getNodeInfo(nodeId, { include: ['allowableOperations'] })
.then((entity: MinimalNodeEntryEntity) => {
this.displayNode = entity;
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
});
}
}
}

View File

@ -14,6 +14,9 @@
[navigate]="false"
[sorting]="[ 'title', 'asc' ]"
[acaSortingPreferenceKey]="sortingPreferenceKey"
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)"
(node-dblclick)="onNodeDoubleClick($event)">
<empty-folder-content>

View File

@ -44,6 +44,9 @@ import { DocumentListService } from '@alfresco/adf-content-services';
import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
import { LibrariesComponent } from './libraries.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('Libraries Routed Component', () => {
let fixture: ComponentFixture<LibrariesComponent>;
@ -78,7 +81,8 @@ describe('Libraries Routed Component', () => {
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
MatSnackBarModule, MatIconModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
DataTableComponent,

View File

@ -29,6 +29,8 @@ import { NodesApiService, UserPreferencesService } from '@alfresco/adf-core';
import { ShareDataRow } from '@alfresco/adf-content-services';
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './libraries.component.html'
@ -37,9 +39,10 @@ export class LibrariesComponent extends PageComponent {
constructor(private nodesApi: NodesApiService,
route: ActivatedRoute,
private router: Router,
store: Store<AcaState>,
router: Router,
preferences: UserPreferencesService) {
super(preferences, route);
super(preferences, router, route, store);
}
makeLibraryTooltip(library: any): string {

View File

@ -24,17 +24,22 @@
*/
import { PageComponent } from './page.component';
import { MinimalNodeEntity } from 'alfresco-js-api';
class TestClass extends PageComponent {
node: any;
setSelection(selection: MinimalNodeEntity[] = []) {
this.onSelectionChanged(selection);
}
constructor() {
super(null, null);
super(null, null, null, null);
}
}
describe('PageComponent', () => {
let component;
let component: TestClass;
beforeEach(() => {
component = new TestClass();
@ -56,47 +61,44 @@ describe('PageComponent', () => {
describe('hasSelection()', () => {
it('returns true when it has nodes selected', () => {
const hasSelection = component.hasSelection([ {}, {} ]);
expect(hasSelection).toBe(true);
component.setSelection([
{ entry: { isFile: true } },
{ entry: { isFile: true } }
]);
expect(component.hasSelection).toBe(true);
});
it('returns false when it has no selections', () => {
const hasSelection = component.hasSelection([]);
expect(hasSelection).toBe(false);
component.setSelection([]);
expect(component.hasSelection).toBe(false);
});
});
describe('isFileSelected()', () => {
describe('firstSelectedDocument', () => {
it('returns true if selected node is file', () => {
const selection = [ { entry: { isFile: true } } ];
expect(component.isFileSelected(selection)).toBe(true);
component.setSelection(selection);
expect(component.firstSelectedDocument).toBe(selection[0]);
});
it('returns false if selected node is folder', () => {
const selection = [ { entry: { isFolder: true } } ];
expect(component.isFileSelected(selection)).toBe(false);
});
it('returns false if there are more than one selections', () => {
const selection = [ { entry: { isFile: true } }, { entry: { isFile: true } } ];
expect(component.isFileSelected(selection)).toBe(false);
const selection = [ { entry: { isFile: false, isFolder: true } } ];
component.setSelection(selection);
expect(component.firstSelectedDocument).toBeFalsy();
});
});
describe('isFolderSelected()', () => {
describe('firstSelectedFolder', () => {
it('returns true if selected node is folder', () => {
const selection = [ { entry: { isFolder: true } } ];
expect(component.isFolderSelected(selection)).toBe(true);
const selection = [ { entry: { isFile: false, isFolder: true } } ];
component.setSelection(selection);
expect(component.firstSelectedFolder).toBe(selection[0]);
});
it('returns false if selected node is file', () => {
const selection = [ { entry: { isFile: true } } ];
expect(component.isFolderSelected(selection)).toBe(false);
});
it('returns false if there are more than one selections', () => {
const selection = [ { entry: { isFolder: true } }, { entry: { isFolder: true } } ];
expect(component.isFolderSelected(selection)).toBe(false);
const selection = [ { entry: { isFile: true, isFolder: false } } ];
component.setSelection(selection);
expect(component.firstSelectedFolder).toBeFalsy();
});
});
});

View File

@ -26,11 +26,18 @@
import { MinimalNodeEntity, MinimalNodeEntryEntity, Pagination } from 'alfresco-js-api';
import { UserPreferencesService } from '@alfresco/adf-core';
import { ShareDataRow, DocumentListComponent } from '@alfresco/adf-content-services';
import { ActivatedRoute } from '@angular/router';
import { OnDestroy, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs/Rx';
import { ActivatedRoute, Router } from '@angular/router';
import { OnDestroy, ViewChild, OnInit } from '@angular/core';
import { Subscription, Subject } from 'rxjs/Rx';
import { Store } from '@ngrx/store';
import { AcaState } from '../store/states/app.state';
import { SetSelectedNodesAction } from '../store/actions/select-nodes.action';
import { selectedNodes } from '../store/selectors/app.selectors';
import { takeUntil } from 'rxjs/operators';
export abstract class PageComponent implements OnDestroy {
export abstract class PageComponent implements OnInit, OnDestroy {
onDestroy$: Subject<void> = new Subject<void>();
@ViewChild(DocumentListComponent)
documentList: DocumentListComponent;
@ -39,6 +46,13 @@ export abstract class PageComponent implements OnDestroy {
infoDrawerOpened = false;
node: MinimalNodeEntryEntity;
hasSelection = false;
firstSelectedDocument: MinimalNodeEntity;
firstSelectedFolder: MinimalNodeEntity;
firstSelectedNode: MinimalNodeEntity;
lastSelectedNode: MinimalNodeEntity;
selectedNodes: MinimalNodeEntity[];
protected subscriptions: Subscription[] = [];
get sortingPreferenceKey(): string {
@ -49,59 +63,82 @@ export abstract class PageComponent implements OnDestroy {
return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK');
}
constructor(protected preferences: UserPreferencesService, protected route: ActivatedRoute) {
constructor(protected preferences: UserPreferencesService,
protected router: Router,
protected route: ActivatedRoute,
protected store: Store<AcaState>) {
}
ngOnInit() {
this.store
.select(selectedNodes)
.pipe(takeUntil(this.onDestroy$))
.subscribe(selection => this.onSelectionChanged(selection));
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.subscriptions = [];
this.onDestroy$.complete();
}
// Precalculates all the "static state" flags so that UI does not re-evaluate that on every tick
protected onSelectionChanged(selection: MinimalNodeEntity[] = []) {
this.selectedNodes = selection;
this.hasSelection = selection.length > 0;
if (selection.length > 0) {
const firstNode = selection[0];
this.firstSelectedNode = firstNode;
this.firstSelectedDocument = selection.find(entity => entity.entry.isFile);
this.firstSelectedFolder = selection.find(entity => entity.entry.isFolder);
} else {
this.firstSelectedNode = null;
this.firstSelectedDocument = null;
this.firstSelectedFolder = null;
this.lastSelectedNode = null;
this.infoDrawerOpened = false;
}
}
showPreview(node: MinimalNodeEntity) {
if (node && node.entry) {
if (node.entry.isFile) {
this.router.navigate(['./preview', node.entry.id], { relativeTo: this.route });
}
}
}
getParentNodeId(): string {
return this.node ? this.node.id : null;
}
hasSelection(selection: Array<MinimalNodeEntity>): boolean {
return selection && selection.length > 0;
}
isFileSelected(selection: Array<MinimalNodeEntity>): boolean {
if (selection && selection.length === 1) {
const entry = selection[0].entry;
if (entry && entry.isFile) {
return true;
}
}
return false;
}
isFolderSelected(selection: Array<MinimalNodeEntity>): boolean {
if (selection && selection.length === 1) {
const entry = selection[0].entry;
if (entry && entry.isFolder) {
return true;
}
}
return false;
}
onChangePageSize(event: Pagination): void {
this.preferences.paginationSize = event.maxItems;
}
onNodeSelect(event, documentList) {
onNodeSelect(event: CustomEvent, documentList: DocumentListComponent) {
if (!!event.detail && !!event.detail.node) {
const node: MinimalNodeEntryEntity = event.detail.node.entry;
if (node && PageComponent.isLockedNode(node)) {
this.unSelectLockedNodes(documentList);
}
this.lastSelectedNode = event.detail.node;
this.store.dispatch(new SetSelectedNodesAction(documentList.selection));
}
}
unSelectLockedNodes(documentList) {
onDocumentListReady(event: CustomEvent, documentList: DocumentListComponent) {
this.store.dispatch(new SetSelectedNodesAction(documentList.selection));
}
onNodeUnselect(event: CustomEvent, documentList: DocumentListComponent) {
this.store.dispatch(new SetSelectedNodesAction(documentList.selection));
}
unSelectLockedNodes(documentList: DocumentListComponent) {
documentList.selection = documentList.selection.filter(item => !PageComponent.isLockedNode(item.entry));
const dataTable = documentList.dataTable;
@ -143,6 +180,7 @@ export abstract class PageComponent implements OnDestroy {
reload(): void {
if (this.documentList) {
this.documentList.resetSelection();
this.store.dispatch(new SetSelectedNodesAction([]));
this.documentList.reload();
}
}

View File

@ -68,7 +68,7 @@
<button
mat-menu-item
*ngIf="permission.check(node, ['update'])"
[acaNodeVersions]="selectedEntities">
[acaNodeVersions]="node">
<mat-icon>history</mat-icon>
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
</button>

View File

@ -3,29 +3,27 @@
<adf-breadcrumb root="APP.BROWSE.RECENT.TITLE">
</adf-breadcrumb>
<adf-toolbar class="inline">
<adf-toolbar class="inline" *ngIf="hasSelection">
<button
mat-icon-button
color="primary"
*ngIf="isFileSelected(documentList.selection)"
*ngIf="firstSelectedDocument"
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
(click)="onNodeDoubleClick(documentList.selection[0]?.entry)">
(click)="showPreview(firstSelectedDocument)">
<mat-icon>open_in_browser</mat-icon>
</button>
<button
mat-icon-button
color="primary"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[adfNodeDownload]="documentList.selection">
[adfNodeDownload]="selectedNodes">
<mat-icon>get_app</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'accent' : 'primary'"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
@ -34,7 +32,6 @@
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.MORE' | translate }}"
[matMenuTriggerFor]="actionsMenu">
<mat-icon>more_vert</mat-icon>
@ -45,7 +42,7 @@
<button
mat-menu-item
#selection="adfFavorite"
[adf-node-favorite]="documentList.selection">
[adf-node-favorite]="selectedNodes">
<mat-icon color="primary" *ngIf="selection.hasFavorites()">star</mat-icon>
<mat-icon *ngIf="!selection.hasFavorites()">star_border</mat-icon>
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
@ -53,31 +50,31 @@
<button
mat-menu-item
[acaCopyNode]="documentList.selection">
[acaCopyNode]="selectedNodes">
<mat-icon>content_copy</mat-icon>
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'])"
[acaMoveNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'])"
[acaMoveNode]="selectedNodes">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'])"
[acaDeleteNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'])"
[acaDeleteNode]="selectedNodes">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="isFileSelected(documentList.selection)"
[acaNodeVersions]="documentList.selection">
*ngIf="firstSelectedDocument"
[acaNodeVersions]="firstSelectedDocument">
<mat-icon>history</mat-icon>
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
</button>
@ -95,7 +92,9 @@
[acaSortingPreferenceKey]="sortingPreferenceKey"
[imageResolver]="imageResolver"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)"
(node-select)="onNodeSelect($event, documentList)">
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)">
<empty-folder-content>
<ng-template>
@ -156,41 +155,8 @@
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[acaNodeInfo]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE' | translate">
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.PROPERTIES' | translate">
<div *ngIf="infoInstance.loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<adf-content-metadata-card
[readOnly]="!permission.check(infoInstance.node, ['update'])"
[displayEmpty]="permission.check(infoInstance.node, ['update'])"
[preset]="'custom'"
[node]="infoInstance.node">
</adf-content-metadata-card>
</adf-info-drawer-tab>
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.VERSIONS' | translate">
<ng-container *ngIf="isFileSelected(documentList.selection);else choose_document_template">
<adf-version-manager
*ngIf="infoInstance.node"
[node]="infoInstance.node">
</adf-version-manager>
</ng-container>
<ng-template #choose_document_template>
<div class="adf-manage-versions-empty">
<mat-icon class="adf-manage-versions-empty-icon">face</mat-icon>
{{ 'VERSION.SELECTION.EMPTY' | translate }}
</div>
</ng-template>
</adf-info-drawer-tab>
</adf-info-drawer>
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
<aca-info-drawer [node]="lastSelectedNode"></aca-info-drawer>
</div>
</div>
</div>

View File

@ -41,10 +41,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { RecentFilesComponent } from './recent-files.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('RecentFiles Routed Component', () => {
let fixture: ComponentFixture<RecentFilesComponent>;
@ -71,14 +73,14 @@ describe('RecentFiles Routed Component', () => {
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
MatSnackBarModule, MatIconModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
RecentFilesComponent,
AppConfigPipe

View File

@ -31,6 +31,8 @@ import { UserPreferencesService } from '@alfresco/adf-core';
import { ContentManagementService } from '../../common/services/content-management.service';
import { PageComponent } from '../page.component';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './recent-files.component.html'
@ -38,15 +40,18 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
export class RecentFilesComponent extends PageComponent implements OnInit {
constructor(
private router: Router,
router: Router,
route: ActivatedRoute,
store: Store<AcaState>,
private content: ContentManagementService,
public permission: NodePermissionService,
preferences: UserPreferencesService) {
super(preferences, route);
super(preferences, router, route, store);
}
ngOnInit() {
super.ngOnInit();
this.subscriptions = this.subscriptions.concat([
this.content.nodeDeleted.subscribe(() => this.reload()),
this.content.nodeMoved.subscribe(() => this.reload()),

View File

@ -3,28 +3,25 @@
<adf-breadcrumb root="APP.BROWSE.SHARED.TITLE">
</adf-breadcrumb>
<adf-toolbar class="inline">
<adf-toolbar class="inline" *ngIf="hasSelection">
<button
color="primary"
mat-icon-button
*ngIf="isFileSelected(documentList.selection)"
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
(click)="onNodeDoubleClick(documentList.selection[0]?.entry)">
(click)="showPreview(selectedNodes[0])">
<mat-icon>open_in_browser</mat-icon>
</button>
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[adfNodeDownload]="documentList.selection">
[adfNodeDownload]="selectedNodes">
<mat-icon>get_app</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'accent' : 'primary'"
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
@ -33,7 +30,6 @@
<button
color="primary"
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.MORE' | translate }}"
[matMenuTriggerFor]="actionsMenu">
<mat-icon>more_vert</mat-icon>
@ -44,7 +40,7 @@
<button
mat-menu-item
#selection="adfFavorite"
[adf-node-favorite]="documentList.selection">
[adf-node-favorite]="selectedNodes">
<mat-icon color="primary" *ngIf="selection.hasFavorites()">star</mat-icon>
<mat-icon *ngIf="!selection.hasFavorites()">star_border</mat-icon>
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
@ -52,23 +48,23 @@
<button
mat-menu-item
[acaCopyNode]="documentList.selection">
[acaCopyNode]="selectedNodes">
<mat-icon>content_copy</mat-icon>
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'], { target: 'allowableOperationsOnTarget' })"
[acaMoveNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'], { target: 'allowableOperationsOnTarget' })"
[acaMoveNode]="selectedNodes">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'])"
[acaUnshareNode]="documentList.selection"
*ngIf="permission.check(selectedNodes, ['delete'])"
[acaUnshareNode]="selectedNodes"
(links-unshared)="reload()">
<mat-icon>stop_screen_share</mat-icon>
<span>{{ 'APP.ACTIONS.UNSHARE' | translate }}</span>
@ -76,16 +72,16 @@
<button
mat-menu-item
*ngIf="permission.check(documentList.selection, ['delete'], { target: 'allowableOperationsOnTarget' })"
[acaDeleteNode]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['delete'], { target: 'allowableOperationsOnTarget' })"
[acaDeleteNode]="selectedNodes">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="permission.check(documentList.selection[0], ['update'], { target: 'allowableOperationsOnTarget' })"
[acaNodeVersions]="documentList.selection">
*ngIf="permission.check(selectedNodes, ['update'], { target: 'allowableOperationsOnTarget' })"
[acaNodeVersions]="lastSelectedNode">
<mat-icon>history</mat-icon>
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
</button>
@ -100,7 +96,10 @@
selectionMode="multiple"
[sorting]="[ 'modifiedAt', 'desc' ]"
[acaSortingPreferenceKey]="sortingPreferenceKey"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)">
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)"
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)">
<empty-folder-content>
<ng-template>
@ -173,41 +172,8 @@
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[acaNodeInfo]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer [title]="'APP.INFO_DRAWER.TITLE' | translate">
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.PROPERTIES' | translate">
<div *ngIf="infoInstance.loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<adf-content-metadata-card
[readOnly]="!permission.check(documentList.selection, ['update'], { target: 'allowableOperationsOnTarget' })"
[displayEmpty]="permission.check(documentList.selection, ['update'], { target: 'allowableOperationsOnTarget' })"
[preset]="'custom'"
[node]="infoInstance.node">
</adf-content-metadata-card>
</adf-info-drawer-tab>
<adf-info-drawer-tab [label]="'APP.INFO_DRAWER.TABS.VERSIONS' | translate">
<ng-container *ngIf="isFileSelected(documentList.selection);else choose_document_template">
<adf-version-manager
*ngIf="infoInstance.node"
[node]="infoInstance.node">
</adf-version-manager>
</ng-container>
<ng-template #choose_document_template>
<div class="adf-manage-versions-empty">
<mat-icon class="adf-manage-versions-empty-icon">face</mat-icon>
{{ 'VERSION.SELECTION.EMPTY' | translate }}
</div>
</ng-template>
</adf-info-drawer-tab>
</adf-info-drawer>
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
<aca-info-drawer [node]="lastSelectedNode"></aca-info-drawer>
</div>
</div>
</div>

View File

@ -41,10 +41,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { SharedFilesComponent } from './shared-files.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('SharedFilesComponent', () => {
let fixture: ComponentFixture<SharedFilesComponent>;
@ -73,14 +75,14 @@ describe('SharedFilesComponent', () => {
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
MatSnackBarModule, MatIconModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
SharedFilesComponent,
AppConfigPipe

View File

@ -31,22 +31,27 @@ import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './shared-files.component.html'
})
export class SharedFilesComponent extends PageComponent implements OnInit {
constructor(private router: Router,
constructor(router: Router,
route: ActivatedRoute,
store: Store<AcaState>,
private content: ContentManagementService,
private apiService: AlfrescoApiService,
public permission: NodePermissionService,
preferences: UserPreferencesService) {
super(preferences, route);
super(preferences, router, route, store);
}
ngOnInit() {
super.ngOnInit();
this.subscriptions = this.subscriptions.concat([
this.content.nodeDeleted.subscribe(() => this.reload()),
this.content.nodeMoved.subscribe(() => this.reload()),
@ -65,9 +70,4 @@ export class SharedFilesComponent extends PageComponent implements OnInit {
);
}
}
/** @override */
isFileSelected(selection: Array<MinimalNodeEntity>): boolean {
return selection && selection.length === 1;
}
}

View File

@ -32,13 +32,14 @@ import { HttpClientModule } from '@angular/common/http';
import {
AppConfigService, AuthenticationService,
UserPreferencesService, StorageService, AlfrescoApiService,
CookieService, LogService, NotificationService
CookieService, LogService, NotificationService, TranslationService, TranslationMock
} from '@alfresco/adf-core';
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
import { NodePermissionService } from '../../common/services/node-permission.service';
import { SidenavComponent } from './sidenav.component';
import { ElectronModule } from '@ngstack/electron';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('SidenavComponent', () => {
let fixture;
@ -58,6 +59,7 @@ describe('SidenavComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
HttpClientModule,
MatMenuModule,
MatSnackBarModule,
@ -69,6 +71,7 @@ describe('SidenavComponent', () => {
SidenavComponent
],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
LogService,
CookieService,
AlfrescoApiService,

View File

@ -3,13 +3,12 @@
<adf-breadcrumb root="APP.BROWSE.TRASHCAN.TITLE">
</adf-breadcrumb>
<adf-toolbar class="inline">
<adf-toolbar class="inline" *ngIf="hasSelection">
<button
color="primary"
mat-icon-button
[app-permanent-delete-node]="documentList.selection"
[app-permanent-delete-node]="selectedNodes"
(selection-node-deleted)="reload()"
*ngIf="documentList.selection.length"
title="{{ 'APP.ACTIONS.DELETE_PERMANENT' | translate }}">
<mat-icon>delete_forever</mat-icon>
</button>
@ -18,8 +17,7 @@
color="primary"
mat-icon-button
(selection-node-restored)="reload()"
[acaRestoreNode]="documentList.selection"
*ngIf="documentList.selection.length"
[acaRestoreNode]="selectedNodes"
title="{{ 'APP.ACTIONS.RESTORE' | translate }}">
<mat-icon>restore</mat-icon>
</button>
@ -33,7 +31,10 @@
selectionMode="multiple"
[navigate]="false"
[sorting]="[ 'archivedAt', 'desc' ]"
[acaSortingPreferenceKey]="sortingPreferenceKey">
[acaSortingPreferenceKey]="sortingPreferenceKey"
(ready)="onDocumentListReady($event, documentList)"
(node-select)="onNodeSelect($event, documentList)"
(node-unselect)="onNodeUnselect($event, documentList)">
<empty-folder-content>
<ng-template>

View File

@ -40,9 +40,11 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { TrashcanComponent } from './trashcan.component';
import { StoreModule } from '@ngrx/store';
import { appReducer } from '../../store/reducers/app.reducer';
import { INITIAL_STATE } from '../../store/states/app.state';
describe('TrashcanComponent', () => {
let fixture: ComponentFixture<TrashcanComponent>;
@ -68,14 +70,14 @@ describe('TrashcanComponent', () => {
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
MatSnackBarModule, MatIconModule,
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE })
],
declarations: [
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
TrashcanComponent,
AppConfigPipe

View File

@ -24,11 +24,13 @@
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Pagination } from 'alfresco-js-api';
import { UserPreferencesService } from '@alfresco/adf-core';
import { ContentManagementService } from '../../common/services/content-management.service';
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AcaState } from '../../store/states/app.state';
@Component({
templateUrl: './trashcan.component.html'
@ -37,11 +39,15 @@ export class TrashcanComponent extends PageComponent implements OnInit {
constructor(private contentManagementService: ContentManagementService,
preferences: UserPreferencesService,
store: Store<AcaState>,
router: Router,
route: ActivatedRoute) {
super(preferences, route);
super(preferences, router, route, store);
}
ngOnInit() {
super.ngOnInit();
this.subscriptions.push(
this.contentManagementService.nodeRestored.subscribe(() => this.reload())
);

View File

@ -0,0 +1,8 @@
import { Action } from '@ngrx/store';
export const SET_SELECTED_NODES = 'SET_SELECTED_NODES';
export class SetSelectedNodesAction implements Action {
readonly type = SET_SELECTED_NODES;
constructor(public payload: any[] = []) {}
}

View File

@ -3,6 +3,7 @@ import { AppState, INITIAL_APP_STATE } from '../states/app.state';
import { SET_HEADER_COLOR, SetHeaderColorAction } from '../actions/header-color.action';
import { SET_APP_NAME, SetAppNameAction } from '../actions/app-name.action';
import { SET_LOGO_PATH, SetLogoPathAction } from '../actions/logo-path.action';
import { SET_SELECTED_NODES, SetSelectedNodesAction } from '../actions/select-nodes.action';
export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action): AppState {
@ -18,6 +19,9 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action):
case SET_LOGO_PATH:
newState = updateLogoPath(state, <SetLogoPathAction> action);
break;
case SET_SELECTED_NODES:
newState = updateSelectedNodes(state, <SetSelectedNodesAction> action);
break;
default:
newState = Object.assign({}, state);
}
@ -42,3 +46,9 @@ function updateLogoPath(state: AppState, action: SetLogoPathAction): AppState {
newState.logoPath = action.payload;
return newState;
}
function updateSelectedNodes(state: AppState, action: SetSelectedNodesAction): AppState {
const newState = Object.assign({}, state);
newState.selectedNodes = [...action.payload];
return newState;
}

View File

@ -5,3 +5,4 @@ export const selectApp = (state: AcaState) => state.app;
export const selectHeaderColor = createSelector(selectApp, (state: AppState) => state.headerColor);
export const selectAppName = createSelector(selectApp, (state: AppState) => state.appName);
export const selectLogoPath = createSelector(selectApp, (state: AppState) => state.logoPath);
export const selectedNodes = createSelector(selectApp, (state: AppState) => state.selectedNodes);

View File

@ -1,13 +1,17 @@
import { MinimalNodeEntity } from 'alfresco-js-api';
export interface AppState {
appName: string;
headerColor: string;
logoPath: string;
selectedNodes: MinimalNodeEntity[];
}
export const INITIAL_APP_STATE: AppState = {
appName: 'Alfresco Example Content Application',
headerColor: '#2196F3',
logoPath: 'assets/images/alfresco-logo-white.svg'
logoPath: 'assets/images/alfresco-logo-white.svg',
selectedNodes: []
};
export interface AcaState {

View File

@ -122,7 +122,7 @@
"component-selector": [
true,
"element",
"app",
[ "app", "aca"],
"kebab-case"
],
"use-input-property-decorator": true,