[ACA-1226] Node info panel (#241)

* node metadata

* getNodeInfo over nodeInfo
This commit is contained in:
Cilibiu Bogdan
2018-03-19 13:16:20 +02:00
committed by Denys Vuika
parent 50d6147e81
commit 086d22b92d
20 changed files with 708 additions and 396 deletions

View File

@@ -38,6 +38,7 @@ import { NodeMoveDirective } from './directives/node-move.directive';
import { NodeRestoreDirective } from './directives/node-restore.directive';
import { NodePermanentDeleteDirective } from './directives/node-permanent-delete.directive';
import { NodeUnshareDirective } from './directives/node-unshare.directive';
import { NodeInfoDirective} from './directives/node-info.directive';
import { ContentManagementService } from './services/content-management.service';
import { BrowsingFilesService } from './services/browsing-files.service';
@@ -61,7 +62,8 @@ export function declarations() {
NodeMoveDirective,
NodeRestoreDirective,
NodePermanentDeleteDirective,
NodeUnshareDirective
NodeUnshareDirective,
NodeInfoDirective
];
}

View File

@@ -0,0 +1,103 @@
/*!
* @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 } from '@alfresco/adf-core';
import { CommonModule } from '../common.module';
@Component({
template: '<div [app-node-info]="selection"></div>'
})
class TestComponent {
selection;
}
describe('NodeInfoDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
let apiService: AlfrescoApiService;
let nodeService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule
],
declarations: [
TestComponent
]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
apiService = TestBed.get(AlfrescoApiService);
nodeService = apiService.getInstance().nodes;
fixture.detectChanges();
}));
beforeEach(() => {
spyOn(nodeService, 'getNodeInfo').and.returnValue(Promise.resolve({
entry: { name: 'borg' }
}));
});
it('should not get node info when selection is empty', () => {
component.selection = [];
fixture.detectChanges();
document.dispatchEvent(new CustomEvent('click'));
expect(nodeService.getNodeInfo).not.toHaveBeenCalled();
});
it('should get node info from selection', () => {
component.selection = [{ entry: { id: 'id' } }];
fixture.detectChanges();
document.dispatchEvent(new CustomEvent('click'));
expect(nodeService.getNodeInfo).toHaveBeenCalledWith('id');
});
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');
}));
});

View File

@@ -0,0 +1,76 @@
/*!
* @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 } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { MinimalNodeEntity, MinimalNodeEntryEntity, Node } from 'alfresco-js-api';
@Directive({
selector: '[app-node-info]',
exportAs: 'nodeInfo'
})
export class NodeInfoDirective {
@Input('app-node-info') selection: MinimalNodeEntity[];
@Output() changed: EventEmitter<null|Node> = new EventEmitter<null|Node>();
@Output() error: EventEmitter<null> = new EventEmitter<null>();
node: Node;
loading: boolean = null;
@HostListener('document:click', ['$event'])
onClick(event) {
this.getNodeInfo();
}
constructor(private apiService: AlfrescoApiService) {}
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)
.then((data: MinimalNodeEntryEntity) => {
this.node = data;
this.changed.emit(data);
this.loading = false;
})
.catch(() => {
this.error.emit();
this.loading = false;
});
}
}
}

View File

@@ -29,6 +29,13 @@
<mat-icon>create</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
@@ -74,7 +81,7 @@
</adf-toolbar>
</div>
<div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-favorites-"
@@ -149,4 +156,18 @@
(changePageSize)="onChangePageSize($event)">
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[app-node-info]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer title="Details">
<adf-info-drawer-tab label="Properties">
<adf-content-metadata-card [node]="infoInstance.node"></adf-content-metadata-card>
</adf-info-drawer-tab>
</adf-info-drawer>
</div>
</div>
</div>

View File

@@ -44,6 +44,7 @@ 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 { FavoritesComponent } from './favorites.component';
@@ -100,6 +101,7 @@ describe('Favorites Routed Component', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
FavoritesComponent
],

View File

@@ -31,6 +31,13 @@
<mat-icon>create</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
@@ -83,6 +90,7 @@
</div>
<div [attr.class]="!isValidPath ? 'content--hide' : 'inner-layout__content'">
<div class="inner-layout__panel">
<adf-upload-drag-area
[parentId]="node?.id"
[disabled]="!canCreateContent(node)">
@@ -155,4 +163,18 @@
</ng-container>
</adf-upload-drag-area>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[app-node-info]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer title="Details">
<adf-info-drawer-tab label="Properties">
<adf-content-metadata-card [node]="infoInstance.node"></adf-content-metadata-card>
</adf-info-drawer-tab>
</adf-info-drawer>
</div>
</div>
</div>

View File

@@ -45,6 +45,7 @@ import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service';
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
import { NodeActionsService } from '../../common/services/node-actions.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { FilesComponent } from './files.component';
@@ -79,6 +80,7 @@ describe('FilesComponent', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
FileSizePipe
],

View File

@@ -7,7 +7,7 @@
</div>
<div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-mysites-"
@@ -71,4 +71,5 @@
(changePageSize)="onChangePageSize($event)">
</adf-pagination>
</div>
</div>
</div>

View File

@@ -33,6 +33,7 @@ export abstract class PageComponent {
isLoading = false;
isEmpty = true;
infoDrawerOpened = false;
paging: NodePaging;
pagination: Pagination;
@@ -204,4 +205,11 @@ export abstract class PageComponent {
return null;
}
toggleSidebar(event) {
if (event) {
return;
}
this.infoDrawerOpened = !this.infoDrawerOpened;
}
}

View File

@@ -21,6 +21,13 @@
<mat-icon>get_app</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
@@ -68,7 +75,7 @@
</div>
<div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-recent-"
@@ -138,4 +145,18 @@
(changePageSize)="onChangePageSize($event)">
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[app-node-info]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer title="Details">
<adf-info-drawer-tab label="Properties">
<adf-content-metadata-card [node]="infoInstance.node"></adf-content-metadata-card>
</adf-info-drawer-tab>
</adf-info-drawer>
</div>
</div>
</div>

View File

@@ -41,6 +41,7 @@ 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 { RecentFilesComponent } from './recent-files.component';
@@ -80,6 +81,7 @@ describe('RecentFiles Routed Component', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
RecentFilesComponent
],

View File

@@ -21,6 +21,13 @@
<mat-icon>get_app</mat-icon>
</button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
@@ -77,6 +84,7 @@
</div>
<div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-sharedlinks-"
@@ -155,4 +163,18 @@
(changePageSize)="onChangePageSize($event)">
</adf-pagination>
</div>
<div class="inner-layout__side-panel"
*ngIf="infoDrawerOpened"
[app-node-info]="documentList.selection"
(changed)="toggleSidebar($event)"
#infoInstance=nodeInfo>
<adf-info-drawer title="Details">
<adf-info-drawer-tab label="Properties">
<adf-content-metadata-card [node]="infoInstance.node"></adf-content-metadata-card>
</adf-info-drawer-tab>
</adf-info-drawer>
</div>
</div>
</div>

View File

@@ -41,6 +41,7 @@ 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 { SharedFilesComponent } from './shared-files.component';
@@ -79,6 +80,7 @@ describe('SharedFilesComponent', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
SharedFilesComponent
],

View File

@@ -25,7 +25,7 @@
</div>
<div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-trashcan-"
@@ -101,4 +101,5 @@
(changePageSize)="onChangePageSize($event)">
</adf-pagination>
</div>
</div>
</div>

View File

@@ -41,6 +41,7 @@ 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';
@@ -76,6 +77,7 @@ describe('TrashcanComponent', () => {
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent,
TrashcanComponent
],

View File

@@ -29,19 +29,25 @@ $app-inner-layout--footer-height: 48px;
}
.content--hide {
display: none;
display: none !important;
}
}
.inner-layout {
.no-border {
border: unset
}
display: flex;
flex: 1;
flex-direction: column;
.adf-info-drawer {
width: 350px;
height: 100%;
overflow-y: auto;
}
.no-border {
border: unset
}
&--scroll {
overflow: auto;
}
@@ -62,12 +68,27 @@ $app-inner-layout--footer-height: 48px;
&__content {
display: flex;
flex-direction: column;
flex-direction: row;
flex: 1;
overflow: hidden;
background: #fff;
}
&__panel {
width: 100%;
height: 100%;
display: flex;
flex:1;
flex-direction: column;
border-right: 1px solid rgba(0, 0, 0, 0.07);
}
&__side-panel {
display: flex;
width: 350px;
height: 100%;
}
&__content--scroll {
overflow: auto;
}

View File

@@ -26,3 +26,4 @@ app-root > ng-component {
@import './overrides/alfresco-upload-dialog';
@import './overrides/toolbar';
@import './overrides/breadcrumb';
@import './overrides/adf-info-drawer';

View File

@@ -0,0 +1,3 @@
.adf-info-drawer-layout {
height: 100%;
}

View File

@@ -35,6 +35,7 @@ adf-upload-drag-area:first-child {
adf-upload-drag-area {
height: 100%;
width: 100%;
& > div {
height: 100%;

View File

@@ -4,8 +4,7 @@
// TODO: review and remove once ADF 2.0.0 is out
&.inline {
.mat-toolbar {
border-left: none !important;
border-right: none !important;
border: none !important;
background: $alfresco-gray-background;
padding: 0;
height: 48px;