diff --git a/src/app/components/info-drawer/info-drawer.component.spec.ts b/src/app/components/info-drawer/info-drawer.component.spec.ts index c11483713..dc2311361 100644 --- a/src/app/components/info-drawer/info-drawer.component.spec.ts +++ b/src/app/components/info-drawer/info-drawer.component.spec.ts @@ -22,11 +22,137 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ - +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; + 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 = { 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 = { + 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 = { + 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 = { + 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(); + })); }); diff --git a/src/app/components/info-drawer/info-drawer.component.ts b/src/app/components/info-drawer/info-drawer.component.ts index d5d77767e..9572d5140 100644 --- a/src/app/components/info-drawer/info-drawer.component.ts +++ b/src/app/components/info-drawer/info-drawer.component.ts @@ -23,27 +23,35 @@ * along with Alfresco. If not, see . */ -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 = []; constructor( + private store: Store, 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 ((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 (node).isLibrary; + } + + private isFavoriteListNode(node: MinimalNodeEntity): boolean { + return !this.isLibraryListNode(node) && (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); + } } diff --git a/src/app/components/info-drawer/info.drawer.module.ts b/src/app/components/info-drawer/info.drawer.module.ts index 4511f357d..5e26a34a6 100644 --- a/src/app/components/info-drawer/info.drawer.module.ts +++ b/src/app/components/info-drawer/info.drawer.module.ts @@ -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 ]; } diff --git a/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.html b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.html new file mode 100644 index 000000000..44729a889 --- /dev/null +++ b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.html @@ -0,0 +1,124 @@ + + +
+
+
+
+ + + {{ 'LIBRARY.DIALOG.FORM.NAME' | translate }} + + + + + {{ form.controls.title.value }} + +
+
+
+
+ +
+
+
+
+ + + {{ 'LIBRARY.DIALOG.FORM.SITE_ID' | translate }} + + + + + {{ form.controls.id.value }} + +
+
+
+
+ +
+
+
+
+ + + {{ 'LIBRARY.DIALOG.FORM.TYPE' | translate }} + + + + + {{ (getVisibilityLabel(form.controls.visibility.value)) | translate }} + +
+
+
+
+ +
+
+
+
+ + + {{ 'LIBRARY.DIALOG.FORM.DESCRIPTION' | translate }} + + + + + {{ form.controls.description?.value }} + +
+
+
+
+
+ + + + + + +
+ + + + + {{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }} + + + + + + + + + + + {{ type.label | translate }} + + + + + + + + + {{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }} + + +
+
+ + + + + +
\ No newline at end of file diff --git a/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts new file mode 100644 index 000000000..9b54f8c65 --- /dev/null +++ b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts @@ -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 . + */ +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; + 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: { + 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: { + id: 'libraryId', + ...siteEntryModel + } + }; + + fixture.detectChanges(); + + expect(component.form.value).toEqual(siteEntryModel); + + component.node = { + entry: { + 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: { + id: 'libraryId', + role: 'SiteManager', + ...siteEntryModel + } + }; + + fixture.detectChanges(); + + component.update(); + + expect(storeMock.dispatch).toHaveBeenCalledWith( + new UpdateLibraryAction(siteEntryModel) + ); + }); + + it('should not update library node if it has no permission', () => { + const siteEntryModel = { + title: 'libraryTitle', + description: 'description', + visibility: 'PRIVATE' + }; + component.node = { + entry: { + id: 'libraryId', + role: 'Consumer', + ...siteEntryModel + } + }; + + fixture.detectChanges(); + + component.update(); + + expect(storeMock.dispatch).not.toHaveBeenCalledWith( + new UpdateLibraryAction(siteEntryModel) + ); + }); + + it('should not update library node if form is invalid', () => { + const siteEntryModel = { + title: 'libraryTitle', + description: 'description', + visibility: 'PRIVATE' + }; + component.node = { + entry: { + id: 'libraryId', + role: 'SiteManager', + ...siteEntryModel + } + }; + + fixture.detectChanges(); + + component.form.controls['title'].setErrors({ maxlength: true }); + + component.update(); + + expect(storeMock.dispatch).not.toHaveBeenCalledWith( + new UpdateLibraryAction(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: { + 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); + }); +}); diff --git a/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts new file mode 100644 index 000000000..e687bb8f1 --- /dev/null +++ b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.ts @@ -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 . + */ + +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) {} + + 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 + }); + } +} diff --git a/src/app/components/info-drawer/library-metadata-tab/library-metadata-tab.component.ts b/src/app/components/info-drawer/library-metadata-tab/library-metadata-tab.component.ts new file mode 100644 index 000000000..317d15a3f --- /dev/null +++ b/src/app/components/info-drawer/library-metadata-tab/library-metadata-tab.component.ts @@ -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 . + */ + +import { Component, Input } from '@angular/core'; +import { SiteEntry } from 'alfresco-js-api'; + +@Component({ + selector: 'app-metadata-tab', + template: + '', + host: { class: 'app-metadata-tab' } +}) +export class LibraryMetadataTabComponent { + @Input() + node: SiteEntry; +} diff --git a/src/app/components/libraries/libraries.component.html b/src/app/components/libraries/libraries.component.html index 273518051..f8b62f252 100644 --- a/src/app/components/libraries/libraries.component.html +++ b/src/app/components/libraries/libraries.component.html @@ -75,5 +75,9 @@ + + diff --git a/src/app/components/libraries/libraries.component.ts b/src/app/components/libraries/libraries.component.ts index 915c96c0a..35e0fbbdc 100644 --- a/src/app/components/libraries/libraries.component.ts +++ b/src/app/components/libraries/libraries.component.ts @@ -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 || []; } diff --git a/src/app/extensions/core.extensions.module.ts b/src/app/extensions/core.extensions.module.ts index 725165020..030901e30 100644 --- a/src/app/extensions/core.extensions.module.ts +++ b/src/app/extensions/core.extensions.module.ts @@ -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, diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index a2224fa6b..825116a21 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -243,7 +243,7 @@ export class AppExtensionService implements RuleContext { } getSidebarTabs(): Array { - return this.sidebar; + return this.sidebar.filter(action => this.filterByRules(action)); } getComponentById(id: string): Type<{}> { diff --git a/src/app/services/content-api.service.ts b/src/app/services/content-api.service.ts index 140e10de4..edf699836 100644 --- a/src/app/services/content-api.service.ts +++ b/src/app/services/content-api.service.ts @@ -219,6 +219,10 @@ export class ContentApiService { return from(this.api.sitesApi.getSite(siteId, opts)); } + updateLibrary(siteId: string, siteBody: SiteBody): Observable { + return from(this.api.sitesApi.updateSite(siteId, siteBody)); + } + addFavorite(nodes: Array): Observable { const payload: FavoriteBody[] = nodes.map(node => { const { isFolder, nodeId, id } = node.entry; diff --git a/src/app/services/content-management.service.ts b/src/app/services/content-management.service.ts index bb589860a..213394124 100644 --- a/src/app/services/content-management.service.ts +++ b/src/app/services/content-management.service.ts @@ -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(); libraryDeleted = new Subject(); libraryCreated = new Subject(); + libraryUpdated = new Subject(); linksUnshared = new Subject(); favoriteAdded = new Subject>(); favoriteRemoved = new Subject>(); @@ -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) { const promises = links.map(link => this.contentApi.deleteSharedLink(link.entry.id).toPromise() diff --git a/src/app/store/actions.ts b/src/app/store/actions.ts index 54c344865..7e41eafa9 100644 --- a/src/app/store/actions.ts +++ b/src/app/store/actions.ts @@ -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'; diff --git a/src/app/store/actions/info-drawer.actions.ts b/src/app/store/actions/info-drawer.actions.ts new file mode 100644 index 000000000..e34acbdb4 --- /dev/null +++ b/src/app/store/actions/info-drawer.actions.ts @@ -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 . + */ + +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) {} +} diff --git a/src/app/store/actions/library.actions.ts b/src/app/store/actions/library.actions.ts index 730819412..655e4040f 100644 --- a/src/app/store/actions/library.actions.ts +++ b/src/app/store/actions/library.actions.ts @@ -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) {} +} diff --git a/src/app/store/effects/library.effects.ts b/src/app/store/effects/library.effects.ts index 4bddb284a..3c9388946 100644 --- a/src/app/store/effects/library.effects.ts +++ b/src/app/store/effects/library.effects.ts @@ -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(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 = { + title, + description, + visibility + }; + + this.content.updateLibrary(id, siteBody); + } + }); + }) + ); } diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts index 219547926..c1b8d9f86 100644 --- a/src/app/store/reducers/app.reducer.ts +++ b/src/app/store/reducers/app.reducer.ts @@ -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, action); break; + case SET_INFO_DRAWER_STATE: + newState = setInfoDrawer(state, action); + break; case TOGGLE_DOCUMENT_DISPLAY_MODE: newState = updateDocumentDisplayMode(state, ( 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 diff --git a/src/assets/app.extensions.json b/src/assets/app.extensions.json index 8181a665d..9c2faa21d 100644 --- a/src/assets/app.extensions.json +++ b/src/assets/app.extensions.json @@ -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": [ diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index cdc2fc30c..cbf9b0d6f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -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" } },