[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 { NodeRestoreDirective } from './directives/node-restore.directive';
import { NodePermanentDeleteDirective } from './directives/node-permanent-delete.directive'; import { NodePermanentDeleteDirective } from './directives/node-permanent-delete.directive';
import { NodeUnshareDirective } from './directives/node-unshare.directive'; import { NodeUnshareDirective } from './directives/node-unshare.directive';
import { NodeInfoDirective} from './directives/node-info.directive';
import { ContentManagementService } from './services/content-management.service'; import { ContentManagementService } from './services/content-management.service';
import { BrowsingFilesService } from './services/browsing-files.service'; import { BrowsingFilesService } from './services/browsing-files.service';
@@ -61,7 +62,8 @@ export function declarations() {
NodeMoveDirective, NodeMoveDirective,
NodeRestoreDirective, NodeRestoreDirective,
NodePermanentDeleteDirective, 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> <mat-icon>create</mat-icon>
</button> </button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button <button
mat-icon-button mat-icon-button
*ngIf="hasSelection(documentList.selection)" *ngIf="hasSelection(documentList.selection)"
@@ -74,7 +81,7 @@
</adf-toolbar> </adf-toolbar>
</div> </div>
<div class="inner-layout__content"> <div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList <adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''" [attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-favorites-" currentFolderId="-favorites-"
@@ -149,4 +156,18 @@
(changePageSize)="onChangePageSize($event)"> (changePageSize)="onChangePageSize($event)">
</adf-pagination> </adf-pagination>
</div> </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> </div>

View File

@@ -44,6 +44,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services'; import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service'; import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { FavoritesComponent } from './favorites.component'; import { FavoritesComponent } from './favorites.component';
@@ -100,6 +101,7 @@ describe('Favorites Routed Component', () => {
TimeAgoPipe, TimeAgoPipe,
NodeNameTooltipPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent, DocumentListComponent,
FavoritesComponent FavoritesComponent
], ],

View File

@@ -31,6 +31,13 @@
<mat-icon>create</mat-icon> <mat-icon>create</mat-icon>
</button> </button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button <button
mat-icon-button mat-icon-button
*ngIf="hasSelection(documentList.selection)" *ngIf="hasSelection(documentList.selection)"
@@ -83,6 +90,7 @@
</div> </div>
<div [attr.class]="!isValidPath ? 'content--hide' : 'inner-layout__content'"> <div [attr.class]="!isValidPath ? 'content--hide' : 'inner-layout__content'">
<div class="inner-layout__panel">
<adf-upload-drag-area <adf-upload-drag-area
[parentId]="node?.id" [parentId]="node?.id"
[disabled]="!canCreateContent(node)"> [disabled]="!canCreateContent(node)">
@@ -155,4 +163,18 @@
</ng-container> </ng-container>
</adf-upload-drag-area> </adf-upload-drag-area>
</div> </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> </div>

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,13 @@
<mat-icon>get_app</mat-icon> <mat-icon>get_app</mat-icon>
</button> </button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button <button
mat-icon-button mat-icon-button
*ngIf="hasSelection(documentList.selection)" *ngIf="hasSelection(documentList.selection)"
@@ -68,7 +75,7 @@
</div> </div>
<div class="inner-layout__content"> <div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList <adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''" [attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-recent-" currentFolderId="-recent-"
@@ -138,4 +145,18 @@
(changePageSize)="onChangePageSize($event)"> (changePageSize)="onChangePageSize($event)">
</adf-pagination> </adf-pagination>
</div> </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> </div>

View File

@@ -41,6 +41,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services'; import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service'; import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { RecentFilesComponent } from './recent-files.component'; import { RecentFilesComponent } from './recent-files.component';
@@ -80,6 +81,7 @@ describe('RecentFiles Routed Component', () => {
TimeAgoPipe, TimeAgoPipe,
NodeNameTooltipPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent, DocumentListComponent,
RecentFilesComponent RecentFilesComponent
], ],

View File

@@ -21,6 +21,13 @@
<mat-icon>get_app</mat-icon> <mat-icon>get_app</mat-icon>
</button> </button>
<button mat-icon-button
[color]="infoDrawerOpened ? 'primary' : 'accent'"
*ngIf="documentList.selection.length"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
<button <button
mat-icon-button mat-icon-button
*ngIf="hasSelection(documentList.selection)" *ngIf="hasSelection(documentList.selection)"
@@ -77,6 +84,7 @@
</div> </div>
<div class="inner-layout__content"> <div class="inner-layout__content">
<div class="inner-layout__panel">
<adf-document-list #documentList <adf-document-list #documentList
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''" [attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-sharedlinks-" currentFolderId="-sharedlinks-"
@@ -155,4 +163,18 @@
(changePageSize)="onChangePageSize($event)"> (changePageSize)="onChangePageSize($event)">
</adf-pagination> </adf-pagination>
</div> </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> </div>

View File

@@ -41,6 +41,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services'; import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service'; import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { SharedFilesComponent } from './shared-files.component'; import { SharedFilesComponent } from './shared-files.component';
@@ -79,6 +80,7 @@ describe('SharedFilesComponent', () => {
TimeAgoPipe, TimeAgoPipe,
NodeNameTooltipPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent, DocumentListComponent,
SharedFilesComponent SharedFilesComponent
], ],

View File

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

View File

@@ -41,6 +41,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material';
import { DocumentListService } from '@alfresco/adf-content-services'; import { DocumentListService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../common/services/content-management.service'; import { ContentManagementService } from '../../common/services/content-management.service';
import { NodeInfoDirective } from '../../common/directives/node-info.directive';
import { TrashcanComponent } from './trashcan.component'; import { TrashcanComponent } from './trashcan.component';
@@ -76,6 +77,7 @@ describe('TrashcanComponent', () => {
TimeAgoPipe, TimeAgoPipe,
NodeNameTooltipPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, NodeFavoriteDirective,
NodeInfoDirective,
DocumentListComponent, DocumentListComponent,
TrashcanComponent TrashcanComponent
], ],

View File

@@ -29,19 +29,25 @@ $app-inner-layout--footer-height: 48px;
} }
.content--hide { .content--hide {
display: none; display: none !important;
} }
} }
.inner-layout { .inner-layout {
.no-border {
border: unset
}
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
.adf-info-drawer {
width: 350px;
height: 100%;
overflow-y: auto;
}
.no-border {
border: unset
}
&--scroll { &--scroll {
overflow: auto; overflow: auto;
} }
@@ -62,12 +68,27 @@ $app-inner-layout--footer-height: 48px;
&__content { &__content {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
background: #fff; 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 { &__content--scroll {
overflow: auto; overflow: auto;
} }

View File

@@ -26,3 +26,4 @@ app-root > ng-component {
@import './overrides/alfresco-upload-dialog'; @import './overrides/alfresco-upload-dialog';
@import './overrides/toolbar'; @import './overrides/toolbar';
@import './overrides/breadcrumb'; @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 { adf-upload-drag-area {
height: 100%; height: 100%;
width: 100%;
& > div { & > div {
height: 100%; height: 100%;

View File

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