mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[ACA-1956] Library - metadata info (#760)
* library metadata panel * add infoDrawer component to Library * set infoDrawer state to false onDestroy * infoDrawer action * update actions index * infoDrawer set state reducer * register Library metadata component to extensions * filter infoDrawer tabs * update library api * library actions and effects * refresh library view on update event * infoDrawer tests * refactor * check permission on update * check permission on update * lint * reference selection library * add parameter type * full width
This commit is contained in:
@@ -22,11 +22,137 @@
|
||||
* 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 { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { InfoDrawerComponent } from './info-drawer.component';
|
||||
import { TestBed, ComponentFixture, async } from '@angular/core/testing';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { SetInfoDrawerStateAction } from '../../store/actions';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('InfoDrawerComponent', () => {
|
||||
it('should be defined', () => {
|
||||
expect(InfoDrawerComponent).toBeDefined();
|
||||
let fixture: ComponentFixture<InfoDrawerComponent>;
|
||||
let component: InfoDrawerComponent;
|
||||
let contentApiService;
|
||||
let tab;
|
||||
let appExtensionService;
|
||||
const storeMock = {
|
||||
dispatch: jasmine.createSpy('dispatch')
|
||||
};
|
||||
const extensionServiceMock = {
|
||||
getSidebarTabs: () => {}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [InfoDrawerComponent],
|
||||
providers: [
|
||||
ContentApiService,
|
||||
{ provide: AppExtensionService, useValue: extensionServiceMock },
|
||||
{ provide: Store, useValue: storeMock }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(InfoDrawerComponent);
|
||||
component = fixture.componentInstance;
|
||||
appExtensionService = TestBed.get(AppExtensionService);
|
||||
contentApiService = TestBed.get(ContentApiService);
|
||||
|
||||
tab = <any>{ title: 'tab1' };
|
||||
spyOn(appExtensionService, 'getSidebarTabs').and.returnValue([tab]);
|
||||
});
|
||||
|
||||
it('should get tabs configuration on initialization', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.tabs).toEqual([tab]);
|
||||
});
|
||||
|
||||
it('should set state to false OnDestroy event', () => {
|
||||
fixture.detectChanges();
|
||||
component.ngOnDestroy();
|
||||
|
||||
expect(storeMock.dispatch).toHaveBeenCalledWith(
|
||||
new SetInfoDrawerStateAction(false)
|
||||
);
|
||||
});
|
||||
|
||||
it('should set displayNode when node is from personal list', () => {
|
||||
spyOn(contentApiService, 'getNodeInfo');
|
||||
const nodeMock = { entry: { id: 'nodeId' } };
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(nodeMock.entry);
|
||||
expect(contentApiService.getNodeInfo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set displayNode when node is library', async(() => {
|
||||
spyOn(contentApiService, 'getNodeInfo');
|
||||
const nodeMock = <any>{
|
||||
entry: { id: 'nodeId' },
|
||||
isLibrary: true
|
||||
};
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(nodeMock);
|
||||
expect(contentApiService.getNodeInfo).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call getNodeInfo() when node is a shared file', async(() => {
|
||||
const response = { entry: { id: 'nodeId' } };
|
||||
spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response));
|
||||
const nodeMock = { entry: { nodeId: 'nodeId' }, isLibrary: false };
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(response);
|
||||
expect(contentApiService.getNodeInfo).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call getNodeInfo() when node is a favorite file', async(() => {
|
||||
const response = { entry: { id: 'nodeId' } };
|
||||
spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response));
|
||||
const nodeMock = <any>{
|
||||
entry: { id: 'nodeId', guid: 'guidId' },
|
||||
isLibrary: false
|
||||
};
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(response);
|
||||
expect(contentApiService.getNodeInfo).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call getNodeInfo() when node is a recent file', async(() => {
|
||||
const response = { entry: { id: 'nodeId' } };
|
||||
spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response));
|
||||
const nodeMock = <any>{
|
||||
entry: {
|
||||
id: 'nodeId',
|
||||
content: { mimeType: 'image/jpeg' }
|
||||
},
|
||||
isLibrary: false
|
||||
};
|
||||
component.node = nodeMock;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.displayNode).toBe(response);
|
||||
expect(contentApiService.getNodeInfo).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
@@ -23,27 +23,35 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { Component, Input, OnChanges, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
MinimalNodeEntity,
|
||||
MinimalNodeEntryEntity,
|
||||
SiteEntry
|
||||
} from 'alfresco-js-api';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { SidebarTabRef } from '@alfresco/adf-extensions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { SetInfoDrawerStateAction } from '../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-info-drawer',
|
||||
templateUrl: './info-drawer.component.html'
|
||||
})
|
||||
export class InfoDrawerComponent implements OnChanges, OnInit {
|
||||
export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
nodeId: string;
|
||||
@Input()
|
||||
node: MinimalNodeEntity;
|
||||
|
||||
isLoading = false;
|
||||
displayNode: MinimalNodeEntryEntity;
|
||||
displayNode: MinimalNodeEntryEntity | SiteEntry;
|
||||
tabs: Array<SidebarTabRef> = [];
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private contentApi: ContentApiService,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
@@ -52,22 +60,31 @@ export class InfoDrawerComponent implements OnChanges, OnInit {
|
||||
this.tabs = this.extensions.getSidebarTabs();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.store.dispatch(new SetInfoDrawerStateAction(false));
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.node) {
|
||||
const entry = this.node.entry;
|
||||
if (entry.nodeId) {
|
||||
this.loadNodeInfo(entry.nodeId);
|
||||
} else if ((<any>entry).guid) {
|
||||
// workaround for Favorite files
|
||||
this.loadNodeInfo(entry.id);
|
||||
} else {
|
||||
// workaround Recent
|
||||
if (this.isTypeImage(entry) && !this.hasAspectNames(entry)) {
|
||||
this.loadNodeInfo(this.node.entry.id);
|
||||
} else {
|
||||
this.setDisplayNode(this.node.entry);
|
||||
}
|
||||
|
||||
if (this.isLibraryListNode(this.node)) {
|
||||
return this.setDisplayNode(this.node);
|
||||
}
|
||||
|
||||
if (this.isSharedFilesNode(this.node)) {
|
||||
return this.loadNodeInfo(entry.nodeId);
|
||||
}
|
||||
|
||||
if (this.isFavoriteListNode(this.node)) {
|
||||
return this.loadNodeInfo(entry.id);
|
||||
}
|
||||
|
||||
if (this.isRecentListFileNode(this.node)) {
|
||||
return this.loadNodeInfo(entry.id);
|
||||
}
|
||||
|
||||
this.setDisplayNode(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +113,23 @@ export class InfoDrawerComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private setDisplayNode(node: MinimalNodeEntryEntity) {
|
||||
private setDisplayNode(node: MinimalNodeEntryEntity | SiteEntry) {
|
||||
this.displayNode = node;
|
||||
}
|
||||
|
||||
private isLibraryListNode(node: SiteEntry): boolean {
|
||||
return (<any>node).isLibrary;
|
||||
}
|
||||
|
||||
private isFavoriteListNode(node: MinimalNodeEntity): boolean {
|
||||
return !this.isLibraryListNode(node) && (<any>node).entry.guid;
|
||||
}
|
||||
|
||||
private isSharedFilesNode(node: MinimalNodeEntity): boolean {
|
||||
return !!node.entry.nodeId;
|
||||
}
|
||||
|
||||
private isRecentListFileNode(node: MinimalNodeEntity): boolean {
|
||||
return this.isTypeImage(node.entry) && !this.hasAspectNames(node.entry);
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,8 @@ import { MaterialModule } from '../../material.module';
|
||||
import { CommentsTabComponent } from './comments-tab/comments-tab.component';
|
||||
import { InfoDrawerComponent } from './info-drawer.component';
|
||||
import { MetadataTabComponent } from './metadata-tab/metadata-tab.component';
|
||||
import { LibraryMetadataTabComponent } from './library-metadata-tab/library-metadata-tab.component';
|
||||
import { LibraryMetadataFormComponent } from './library-metadata-tab/library-metadata-form.component';
|
||||
import { VersionsTabComponent } from './versions-tab/versions-tab.component';
|
||||
|
||||
export function components() {
|
||||
@@ -43,7 +45,9 @@ export function components() {
|
||||
InfoDrawerComponent,
|
||||
MetadataTabComponent,
|
||||
CommentsTabComponent,
|
||||
VersionsTabComponent
|
||||
VersionsTabComponent,
|
||||
LibraryMetadataTabComponent,
|
||||
LibraryMetadataFormComponent
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,124 @@
|
||||
<mat-card *ngIf="node">
|
||||
<mat-card-content *ngIf="!edit">
|
||||
<div class="mat-form-field mat-form-field-type-mat-input mat-form-field-can-float mat-form-field-should-float adf-full-width">
|
||||
<div class="mat-form-field-wrapper">
|
||||
<div class="mat-form-field-flex">
|
||||
<div class="mat-form-field-infix">
|
||||
<span class="mat-form-field-label-wrapper">
|
||||
<span class="mat-form-field-label">
|
||||
{{ 'LIBRARY.DIALOG.FORM.NAME' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="mat-input-element">
|
||||
{{ form.controls.title.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-form-field mat-primary mat-form-field-type-mat-input mat-form-field-can-float mat-form-field-should-float adf-full-width">
|
||||
<div class="mat-form-field-wrapper">
|
||||
<div class="mat-form-field-flex">
|
||||
<div class="mat-form-field-infix">
|
||||
<span class="mat-form-field-label-wrapper">
|
||||
<span class="mat-form-field-label">
|
||||
{{ 'LIBRARY.DIALOG.FORM.SITE_ID' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="mat-input-element">
|
||||
{{ form.controls.id.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-form-field mat-primary mat-form-field-type-mat-input mat-form-field-can-float mat-form-field-should-float adf-full-width">
|
||||
<div class="mat-form-field-wrapper">
|
||||
<div class="mat-form-field-flex">
|
||||
<div class="mat-form-field-infix">
|
||||
<span class="mat-form-field-label-wrapper">
|
||||
<span class="mat-form-field-label">
|
||||
{{ 'LIBRARY.DIALOG.FORM.TYPE' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="mat-input-element">
|
||||
{{ (getVisibilityLabel(form.controls.visibility.value)) | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-form-field mat-primary mat-form-field-type-mat-input mat-form-field-can-float mat-form-field-should-float adf-full-width">
|
||||
<div class="mat-form-field-wrapper">
|
||||
<div class="mat-form-field-flex">
|
||||
<div class="mat-form-field-infix">
|
||||
<span class="mat-form-field-label-wrapper">
|
||||
<span class="mat-form-field-label">
|
||||
{{ 'LIBRARY.DIALOG.FORM.DESCRIPTION' | translate }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="mat-input-element">
|
||||
{{ form.controls.description?.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end" *ngIf="!edit">
|
||||
<button mat-button color="primary" (click)="toggleEdit()">
|
||||
{{ 'LIBRARY.DIALOG.EDIT' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
|
||||
<mat-card-content *ngIf="edit">
|
||||
<form [formGroup]="form" autocomplete="off">
|
||||
<mat-form-field class="adf-full-width">
|
||||
<input matInput cdkFocusInitial required placeholder="{{ 'LIBRARY.DIALOG.FORM.NAME' | translate }}"
|
||||
formControlName="title">
|
||||
|
||||
<mat-error *ngIf="form.controls['title'].hasError('maxlength')">
|
||||
{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<input matInput placeholder="{{ 'LIBRARY.DIALOG.FORM.SITE_ID' | translate }}" formControlName="id">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-select placeholder="{{ 'LIBRARY.DIALOG.FORM.TYPE' | translate }}" formControlName="visibility">
|
||||
<mat-option [value]="type.value" *ngFor="let type of libraryType">
|
||||
{{ type.label | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<textarea matInput placeholder="{{ 'LIBRARY.DIALOG.FORM.DESCRIPTION' | translate }}" rows="3"
|
||||
formControlName="description"></textarea>
|
||||
|
||||
<mat-error *ngIf="form.controls['description'].hasError('maxlength')">
|
||||
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end" *ngIf="edit && canUpdateLibrary">
|
||||
<button mat-button color="secondary" (click)="cancel()">
|
||||
{{ 'LIBRARY.DIALOG.CANCEL' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary" [disabled]="form.invalid" (click)="update()">
|
||||
{{ 'LIBRARY.DIALOG.UPDATE' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
@@ -0,0 +1,214 @@
|
||||
/*!
|
||||
* @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 { LibraryMetadataFormComponent } from './library-metadata-form.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { UpdateLibraryAction } from '../../../store/actions';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Site, SiteBody } from 'alfresco-js-api';
|
||||
|
||||
describe('LibraryMetadataFormComponent', () => {
|
||||
let fixture: ComponentFixture<LibraryMetadataFormComponent>;
|
||||
let component: LibraryMetadataFormComponent;
|
||||
const storeMock = {
|
||||
dispatch: jasmine.createSpy('dispatch')
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [LibraryMetadataFormComponent],
|
||||
providers: [{ provide: Store, useValue: storeMock }],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(LibraryMetadataFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
storeMock.dispatch.calls.reset();
|
||||
});
|
||||
|
||||
it('should initialize form with node data', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.value).toEqual(siteEntryModel);
|
||||
});
|
||||
|
||||
it('should update form data when node data changes', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
|
||||
const newSiteEntryModel = {
|
||||
title: 'libraryTitle2',
|
||||
description: 'description2',
|
||||
visibility: 'PUBLIC'
|
||||
};
|
||||
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.value).toEqual(siteEntryModel);
|
||||
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
...newSiteEntryModel
|
||||
}
|
||||
};
|
||||
|
||||
component.ngOnChanges();
|
||||
|
||||
expect(component.form.value).toEqual(newSiteEntryModel);
|
||||
});
|
||||
|
||||
it('should update library node if form is valid', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
role: 'SiteManager',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.update();
|
||||
|
||||
expect(storeMock.dispatch).toHaveBeenCalledWith(
|
||||
new UpdateLibraryAction(<SiteBody>siteEntryModel)
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update library node if it has no permission', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
role: 'Consumer',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.update();
|
||||
|
||||
expect(storeMock.dispatch).not.toHaveBeenCalledWith(
|
||||
new UpdateLibraryAction(<SiteBody>siteEntryModel)
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update library node if form is invalid', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
role: 'SiteManager',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.form.controls['title'].setErrors({ maxlength: true });
|
||||
|
||||
component.update();
|
||||
|
||||
expect(storeMock.dispatch).not.toHaveBeenCalledWith(
|
||||
new UpdateLibraryAction(<SiteBody>siteEntryModel)
|
||||
);
|
||||
});
|
||||
|
||||
it('should toggle edit mode', () => {
|
||||
component.edit = false;
|
||||
|
||||
component.toggleEdit();
|
||||
expect(component.edit).toBe(true);
|
||||
|
||||
component.toggleEdit();
|
||||
expect(component.edit).toBe(false);
|
||||
});
|
||||
|
||||
it('should cancel from changes', () => {
|
||||
const siteEntryModel = {
|
||||
title: 'libraryTitle',
|
||||
description: 'description',
|
||||
visibility: 'PRIVATE'
|
||||
};
|
||||
component.node = {
|
||||
entry: <Site>{
|
||||
id: 'libraryId',
|
||||
...siteEntryModel
|
||||
}
|
||||
};
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.form.value).toEqual(siteEntryModel);
|
||||
|
||||
component.form.controls.title.setValue('libraryTitle-edit');
|
||||
|
||||
expect(component.form.value.title).toBe('libraryTitle-edit');
|
||||
|
||||
component.cancel();
|
||||
|
||||
expect(component.form.value).toEqual(siteEntryModel);
|
||||
});
|
||||
});
|
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* @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, OnInit, OnChanges } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { SiteEntry } from 'alfresco-js-api';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { UpdateLibraryAction } from '../../../store/actions';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-library-metadata-form',
|
||||
templateUrl: './library-metadata-form.component.html'
|
||||
})
|
||||
export class LibraryMetadataFormComponent implements OnInit, OnChanges {
|
||||
@Input()
|
||||
node: SiteEntry;
|
||||
|
||||
edit: boolean;
|
||||
|
||||
libraryType = [
|
||||
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC' },
|
||||
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE' },
|
||||
{ value: 'MODERATED', label: 'LIBRARY.VISIBILITY.MODERATED' }
|
||||
];
|
||||
|
||||
form: FormGroup = new FormGroup({
|
||||
id: new FormControl({ value: '', disabled: true }),
|
||||
title: new FormControl({ value: '' }, [
|
||||
Validators.required,
|
||||
Validators.maxLength(256)
|
||||
]),
|
||||
description: new FormControl({ value: '' }, [Validators.maxLength(512)]),
|
||||
visibility: new FormControl(this.libraryType[0].value)
|
||||
});
|
||||
|
||||
constructor(protected store: Store<AppStore>) {}
|
||||
|
||||
get canUpdateLibrary() {
|
||||
return (
|
||||
this.node && this.node.entry && this.node.entry.role === 'SiteManager'
|
||||
);
|
||||
}
|
||||
|
||||
getVisibilityLabel(value) {
|
||||
return this.libraryType.find(type => type.value === value).label;
|
||||
}
|
||||
|
||||
toggleEdit() {
|
||||
this.edit = !this.edit;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.updateForm(this.node);
|
||||
this.toggleEdit();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.updateForm(this.node);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateForm(this.node);
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.canUpdateLibrary && this.form.valid) {
|
||||
this.store.dispatch(new UpdateLibraryAction(this.form.value));
|
||||
}
|
||||
}
|
||||
|
||||
private updateForm(node: SiteEntry) {
|
||||
const { entry } = node;
|
||||
|
||||
this.form.setValue({
|
||||
id: entry.id,
|
||||
title: entry.title,
|
||||
description: entry.description || '',
|
||||
visibility: entry.visibility
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @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 } from '@angular/core';
|
||||
import { SiteEntry } from 'alfresco-js-api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-metadata-tab',
|
||||
template:
|
||||
'<app-library-metadata-form [node]="node"></app-library-metadata-form>',
|
||||
host: { class: 'app-metadata-tab' }
|
||||
})
|
||||
export class LibraryMetadataTabComponent {
|
||||
@Input()
|
||||
node: SiteEntry;
|
||||
}
|
@@ -75,5 +75,9 @@
|
||||
<adf-pagination acaPagination [target]="documentList">
|
||||
</adf-pagination>
|
||||
</div>
|
||||
|
||||
<div class="sidebar" *ngIf="infoDrawerOpened$ | async">
|
||||
<aca-info-drawer [node]="selection.library"></aca-info-drawer>
|
||||
</div>
|
||||
</app-page-layout-content>
|
||||
</app-page-layout>
|
||||
|
@@ -63,6 +63,10 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.libraryUpdated.subscribe(() => this.documentList.reload())
|
||||
]);
|
||||
|
||||
this.columns = this.extensions.documentListPresets.libraries || [];
|
||||
}
|
||||
|
||||
|
@@ -35,6 +35,7 @@ import { ToggleInfoDrawerComponent } from '../components/toolbar/toggle-info-dra
|
||||
import { ToggleFavoriteComponent } from '../components/toolbar/toggle-favorite/toggle-favorite.component';
|
||||
import { ToggleSharedComponent } from '../components/shared/toggle-shared/toggle-shared.component';
|
||||
import { MetadataTabComponent } from '../components/info-drawer/metadata-tab/metadata-tab.component';
|
||||
import { LibraryMetadataTabComponent } from '../components/info-drawer/library-metadata-tab/library-metadata-tab.component';
|
||||
import { CommentsTabComponent } from '../components/info-drawer/comments-tab/comments-tab.component';
|
||||
import { VersionsTabComponent } from '../components/info-drawer/versions-tab/versions-tab.component';
|
||||
import { ExtensionsModule, ExtensionService } from '@alfresco/adf-extensions';
|
||||
@@ -77,6 +78,7 @@ export class CoreExtensionsModule {
|
||||
extensions.setComponents({
|
||||
'app.layout.main': AppLayoutComponent,
|
||||
'app.components.tabs.metadata': MetadataTabComponent,
|
||||
'app.components.tabs.library.metadata': LibraryMetadataTabComponent,
|
||||
'app.components.tabs.comments': CommentsTabComponent,
|
||||
'app.components.tabs.versions': VersionsTabComponent,
|
||||
'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent,
|
||||
|
@@ -243,7 +243,7 @@ export class AppExtensionService implements RuleContext {
|
||||
}
|
||||
|
||||
getSidebarTabs(): Array<SidebarTabRef> {
|
||||
return this.sidebar;
|
||||
return this.sidebar.filter(action => this.filterByRules(<any>action));
|
||||
}
|
||||
|
||||
getComponentById(id: string): Type<{}> {
|
||||
|
@@ -219,6 +219,10 @@ export class ContentApiService {
|
||||
return from(this.api.sitesApi.getSite(siteId, opts));
|
||||
}
|
||||
|
||||
updateLibrary(siteId: string, siteBody: SiteBody): Observable<SiteEntry> {
|
||||
return from(this.api.sitesApi.updateSite(siteId, siteBody));
|
||||
}
|
||||
|
||||
addFavorite(nodes: Array<MinimalNodeEntity>): Observable<any> {
|
||||
const payload: FavoriteBody[] = nodes.map(node => {
|
||||
const { isFolder, nodeId, id } = node.entry;
|
||||
|
@@ -49,7 +49,8 @@ import {
|
||||
Node,
|
||||
SiteEntry,
|
||||
DeletedNodesPaging,
|
||||
PathInfoEntity
|
||||
PathInfoEntity,
|
||||
SiteBody
|
||||
} from 'alfresco-js-api';
|
||||
import { NodePermissionService } from './node-permission.service';
|
||||
import { NodeInfo, DeletedNodeInfo, DeleteStatus } from '../store/models';
|
||||
@@ -80,6 +81,7 @@ export class ContentManagementService {
|
||||
folderCreated = new Subject<any>();
|
||||
libraryDeleted = new Subject<string>();
|
||||
libraryCreated = new Subject<SiteEntry>();
|
||||
libraryUpdated = new Subject<SiteEntry>();
|
||||
linksUnshared = new Subject<any>();
|
||||
favoriteAdded = new Subject<Array<MinimalNodeEntity>>();
|
||||
favoriteRemoved = new Subject<Array<MinimalNodeEntity>>();
|
||||
@@ -295,6 +297,22 @@ export class ContentManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
updateLibrary(siteId: string, siteBody: SiteBody) {
|
||||
this.contentApi.updateLibrary(siteId, siteBody).subscribe(
|
||||
(siteEntry: SiteEntry) => {
|
||||
this.libraryUpdated.next(siteEntry);
|
||||
this.store.dispatch(
|
||||
new SnackbarInfoAction('LIBRARY.SUCCESS.LIBRARY_UPDATED')
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.store.dispatch(
|
||||
new SnackbarErrorAction('LIBRARY.ERRORS.LIBRARY_UPDATE_ERROR')
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async unshareNodes(links: Array<MinimalNodeEntity>) {
|
||||
const promises = links.map(link =>
|
||||
this.contentApi.deleteSharedLink(link.entry.id).toPromise()
|
||||
|
@@ -34,3 +34,4 @@ export * from './actions/library.actions';
|
||||
export * from './actions/upload.actions';
|
||||
export * from './actions/modals.actions';
|
||||
export * from './actions/repository.actions';
|
||||
export * from './actions/info-drawer.actions';
|
||||
|
33
src/app/store/actions/info-drawer.actions.ts
Normal file
33
src/app/store/actions/info-drawer.actions.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*!
|
||||
* @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 { Action } from '@ngrx/store';
|
||||
|
||||
export const SET_INFO_DRAWER_STATE = 'SET_INFO_DRAWER_STATE';
|
||||
|
||||
export class SetInfoDrawerStateAction implements Action {
|
||||
readonly type = SET_INFO_DRAWER_STATE;
|
||||
constructor(public payload: boolean) {}
|
||||
}
|
@@ -24,10 +24,12 @@
|
||||
*/
|
||||
|
||||
import { Action } from '@ngrx/store';
|
||||
import { SiteBody } from 'alfresco-js-api';
|
||||
|
||||
export const DELETE_LIBRARY = 'DELETE_LIBRARY';
|
||||
export const CREATE_LIBRARY = 'CREATE_LIBRARY';
|
||||
export const NAVIGATE_LIBRARY = 'NAVIGATE_LIBRARY';
|
||||
export const UPDATE_LIBRARY = 'UPDATE_LIBRARY';
|
||||
|
||||
export class DeleteLibraryAction implements Action {
|
||||
readonly type = DELETE_LIBRARY;
|
||||
@@ -43,3 +45,7 @@ export class NavigateLibraryAction implements Action {
|
||||
readonly type = NAVIGATE_LIBRARY;
|
||||
constructor(public payload?: string) {}
|
||||
}
|
||||
export class UpdateLibraryAction implements Action {
|
||||
readonly type = UPDATE_LIBRARY;
|
||||
constructor(public payload?: SiteBody) {}
|
||||
}
|
||||
|
@@ -32,7 +32,9 @@ import {
|
||||
CreateLibraryAction,
|
||||
CREATE_LIBRARY,
|
||||
NavigateLibraryAction,
|
||||
NAVIGATE_LIBRARY
|
||||
NAVIGATE_LIBRARY,
|
||||
UPDATE_LIBRARY,
|
||||
UpdateLibraryAction
|
||||
} from '../actions';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
@@ -40,6 +42,7 @@ import { AppStore } from '../states';
|
||||
import { appSelection } from '../selectors/app.selectors';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { SiteBody } from 'alfresco-js-api-node';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryEffects {
|
||||
@@ -92,4 +95,28 @@ export class LibraryEffects {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@Effect({ dispatch: false })
|
||||
updateLibrary$ = this.actions$.pipe(
|
||||
ofType<UpdateLibraryAction>(UPDATE_LIBRARY),
|
||||
map(action => {
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe(selection => {
|
||||
if (selection && selection.library) {
|
||||
const { id } = selection.library.entry;
|
||||
const { title, description, visibility } = action.payload;
|
||||
|
||||
const siteBody = <SiteBody>{
|
||||
title,
|
||||
description,
|
||||
visibility
|
||||
};
|
||||
|
||||
this.content.updateLibrary(id, siteBody);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@@ -37,7 +37,9 @@ import {
|
||||
SET_CURRENT_FOLDER,
|
||||
SetCurrentFolderAction,
|
||||
SET_CURRENT_URL,
|
||||
SetCurrentUrlAction
|
||||
SetCurrentUrlAction,
|
||||
SET_INFO_DRAWER_STATE,
|
||||
SetInfoDrawerStateAction
|
||||
} from '../actions';
|
||||
import {
|
||||
TOGGLE_INFO_DRAWER,
|
||||
@@ -76,6 +78,9 @@ export function appReducer(
|
||||
case TOGGLE_INFO_DRAWER:
|
||||
newState = updateInfoDrawer(state, <ToggleInfoDrawerAction>action);
|
||||
break;
|
||||
case SET_INFO_DRAWER_STATE:
|
||||
newState = setInfoDrawer(state, <SetInfoDrawerStateAction>action);
|
||||
break;
|
||||
case TOGGLE_DOCUMENT_DISPLAY_MODE:
|
||||
newState = updateDocumentDisplayMode(state, <ToggleDocumentDisplayMode>(
|
||||
action
|
||||
@@ -215,6 +220,12 @@ function updateSelectedNodes(
|
||||
return newState;
|
||||
}
|
||||
|
||||
function setInfoDrawer(state: AppState, action: SetInfoDrawerStateAction) {
|
||||
const newState = Object.assign({}, state);
|
||||
newState.infoDrawerOpened = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateRepositoryStatus(
|
||||
state: AppState,
|
||||
action: SetRepositoryStatusAction
|
||||
|
@@ -99,6 +99,14 @@
|
||||
{ "type": "rule", "value": "app.navigation.isNotTrashcan" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "app.libraries.toolbar.info",
|
||||
"type": "core.every",
|
||||
"parameters": [
|
||||
{ "type": "rule", "value": "app.selection.notEmpty" },
|
||||
{ "type": "rule", "value": "app.navigation.isLibraries" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.canCopyNode",
|
||||
"type": "core.every",
|
||||
@@ -361,6 +369,15 @@
|
||||
"visible": "app.toolbar.info"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.libraries.toolbar.info",
|
||||
"type": "custom",
|
||||
"order": 701,
|
||||
"component": "app.toolbar.toggleInfoDrawer",
|
||||
"rules": {
|
||||
"visible": "app.libraries.toolbar.info"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.toolbar.more",
|
||||
"type": "menu",
|
||||
@@ -827,20 +844,38 @@
|
||||
"id": "app.sidebar.properties",
|
||||
"order": 100,
|
||||
"title": "APP.INFO_DRAWER.TABS.PROPERTIES",
|
||||
"component": "app.components.tabs.metadata"
|
||||
"component": "app.components.tabs.metadata",
|
||||
"rules": {
|
||||
"visible": "app.navigation.isNotLibraries"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.sidebar.comments",
|
||||
"order": 200,
|
||||
"title": "APP.INFO_DRAWER.TABS.COMMENTS",
|
||||
"component": "app.components.tabs.comments"
|
||||
"component": "app.components.tabs.comments",
|
||||
"rules": {
|
||||
"visible": "app.navigation.isNotLibraries"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "app.sidebar.versions",
|
||||
"order": 300,
|
||||
"disabled": true,
|
||||
"title": "APP.INFO_DRAWER.TABS.VERSIONS",
|
||||
"component": "app.components.tabs.versions"
|
||||
"component": "app.components.tabs.versions",
|
||||
"rules": {
|
||||
"visible": "app.navigation.isNotLibraries"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"order": 500,
|
||||
"title": "APP.INFO_DRAWER.TABS.LIBRARY_PROPERTIES",
|
||||
"component": "app.components.tabs.library.metadata",
|
||||
"rules": {
|
||||
"visible": "app.libraries.toolbar.info"
|
||||
}
|
||||
}
|
||||
],
|
||||
"content-metadata-presets": [
|
||||
|
@@ -245,6 +245,7 @@
|
||||
"TITLE": "Details",
|
||||
"TABS": {
|
||||
"PROPERTIES": "Properties",
|
||||
"LIBRARY_PROPERTIES": "About",
|
||||
"VERSIONS": "Versions",
|
||||
"COMMENTS": "Comments"
|
||||
}
|
||||
@@ -282,11 +283,14 @@
|
||||
"DIALOG": {
|
||||
"CREATE_TITLE": "Create Library",
|
||||
"CREATE": "Create",
|
||||
"UPDATE": "Update",
|
||||
"EDIT": "Edit",
|
||||
"CANCEL": "Cancel",
|
||||
"FORM": {
|
||||
"DESCRIPTION": "Description",
|
||||
"SITE_ID": "Library ID",
|
||||
"NAME": "Name"
|
||||
"NAME": "Name",
|
||||
"TYPE": "Type"
|
||||
}
|
||||
},
|
||||
"VISIBILITY": {
|
||||
@@ -301,7 +305,11 @@
|
||||
"ID_TOO_LONG": "Use 72 characters or less for the URL name",
|
||||
"DESCRIPTION_TOO_LONG": "Use 512 characters or less for description",
|
||||
"TITLE_TOO_LONG": "Use 256 characters or less for title",
|
||||
"ILLEGAL_CHARACTERS": "Use numbers and letters only"
|
||||
"ILLEGAL_CHARACTERS": "Use numbers and letters only",
|
||||
"LIBRARY_UPDATE_ERROR": "There was an error updating library properties"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"LIBRARY_UPDATED": "Library properties updated"
|
||||
}
|
||||
},
|
||||
|
||||
|
Reference in New Issue
Block a user