diff --git a/docs/extending/rules.md b/docs/extending/rules.md index 7096c6c3d..7ca46ea0b 100644 --- a/docs/extending/rules.md +++ b/docs/extending/rules.md @@ -166,6 +166,8 @@ The button will be visible only when the linked rule evaluates to `true`. | 1.8.0 | canManagePermissions | Checks if user can manage permissions for the selected node. | | 1.8.0 | canToggleEditOffline | Checks if user can toggle **Edit Offline** mode for selected node. | | 1.8.0 | user.isAdmin | Checks if user is admin. | +| 1.9.0 | app.canShowLanguagePicker | Whether language picker menu should be present or not. | +| 1.9.0 | app.canShowLogout | Whether logout action should be present or not. | ## Navigation Evaluators diff --git a/extension.schema.json b/extension.schema.json index d9fe567aa..99f2bebcf 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -676,6 +676,12 @@ "items": { "$ref": "#/definitions/iconRef" }, "minItems": 1 }, + "userActions": { + "description": "User option menu extensions", + "type": "array", + "items": { "$ref": "#/definitions/contentActionRef" }, + "minItems": 1 + }, "header": { "description": "Application header extensions", "type": "array", diff --git a/projects/aca-shared/rules/src/app.rules.spec.ts b/projects/aca-shared/rules/src/app.rules.spec.ts index 3306e147e..fc307bacd 100644 --- a/projects/aca-shared/rules/src/app.rules.spec.ts +++ b/projects/aca-shared/rules/src/app.rules.spec.ts @@ -439,4 +439,40 @@ describe('app.evaluators', () => { expect(app.isShared(context)).toBe(true); }); }); + + describe('canShowLanguagePicker', () => { + it('should return true when property is true', () => { + const context: any = { + languagePicker: true + }; + + expect(app.canShowLanguagePicker(context)).toBe(true); + }); + + it('should return false when property is false', () => { + const context: any = { + languagePicker: false + }; + + expect(app.canShowLanguagePicker(context)).toBe(false); + }); + }); + + describe('canShowLogout', () => { + it('should return false when `withCredentials` property is true', () => { + const context: any = { + withCredentials: true + }; + + expect(app.canShowLogout(context)).toBe(false); + }); + + it('should return true when `withCredentials` property is false', () => { + const context: any = { + withCredentials: false + }; + + expect(app.canShowLanguagePicker(context)).toBe(true); + }); + }); }); diff --git a/projects/aca-shared/rules/src/app.rules.ts b/projects/aca-shared/rules/src/app.rules.ts index 426d49eab..3c53c5ca4 100644 --- a/projects/aca-shared/rules/src/app.rules.ts +++ b/projects/aca-shared/rules/src/app.rules.ts @@ -27,6 +27,11 @@ import { RuleContext } from '@alfresco/adf-extensions'; import * as navigation from './navigation.rules'; import * as repository from './repository.rules'; +export interface AcaRuleContext extends RuleContext { + languagePicker: boolean; + withCredentials: boolean; +} + /** * Checks if user can copy selected node. * JSON ref: `app.canCopyNode` @@ -526,3 +531,21 @@ export function canToggleFavorite(context: RuleContext): boolean { ].some(Boolean) ].every(Boolean); } + +/** + * Checks if application should render language picker menu. + * JSON ref: `canShowLanguagePicker` + * @param context Rule execution context + */ +export function canShowLanguagePicker(context: AcaRuleContext): boolean { + return context.languagePicker; +} + +/** + * Checks if application should render logout option. + * JSON ref: `canShowLogout` + * @param context Rule execution context + */ +export function canShowLogout(context: AcaRuleContext): boolean { + return !context.withCredentials; +} diff --git a/src/app/components/common/common.module.ts b/src/app/components/common/common.module.ts index 17ac4f12f..a1c972395 100644 --- a/src/app/components/common/common.module.ts +++ b/src/app/components/common/common.module.ts @@ -30,6 +30,8 @@ import { NgModule } from '@angular/core'; import { GenericErrorModule } from '@alfresco/aca-shared'; import { LocationLinkComponent } from './location-link/location-link.component'; import { ToggleSharedComponent } from './toggle-shared/toggle-shared.component'; +import { LanguagePickerComponent } from './language-picker/language-picker.component'; +import { LogoutComponent } from './logout/logout.component'; @NgModule({ imports: [ @@ -38,13 +40,25 @@ import { ToggleSharedComponent } from './toggle-shared/toggle-shared.component'; ExtensionsModule, GenericErrorModule ], - declarations: [LocationLinkComponent, ToggleSharedComponent], + declarations: [ + LocationLinkComponent, + ToggleSharedComponent, + LanguagePickerComponent, + LogoutComponent + ], exports: [ ExtensionsModule, LocationLinkComponent, GenericErrorModule, - ToggleSharedComponent + ToggleSharedComponent, + LanguagePickerComponent, + LogoutComponent ], - entryComponents: [LocationLinkComponent, ToggleSharedComponent] + entryComponents: [ + LocationLinkComponent, + ToggleSharedComponent, + LanguagePickerComponent, + LogoutComponent + ] }) export class AppCommonModule {} diff --git a/src/app/components/common/language-picker/language-picker.component.ts b/src/app/components/common/language-picker/language-picker.component.ts new file mode 100644 index 000000000..2cf2db380 --- /dev/null +++ b/src/app/components/common/language-picker/language-picker.component.ts @@ -0,0 +1,39 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2019 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 } from '@angular/core'; + +@Component({ + selector: 'aca-language-picker', + template: ` + + + + + ` +}) +export class LanguagePickerComponent {} diff --git a/src/app/components/common/logout/logout.component.spec.ts b/src/app/components/common/logout/logout.component.spec.ts new file mode 100644 index 000000000..f5bbb839c --- /dev/null +++ b/src/app/components/common/logout/logout.component.spec.ts @@ -0,0 +1,70 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2019 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 { TestBed, ComponentFixture } from '@angular/core/testing'; +import { + TranslateModule, + TranslateLoader, + TranslateFakeLoader +} from '@ngx-translate/core'; +import { LogoutComponent } from './logout.component'; +import { Store } from '@ngrx/store'; +import { SetSelectedNodesAction } from '@alfresco/aca-shared/store'; + +describe('LogoutComponent', () => { + let fixture: ComponentFixture; + let component: LogoutComponent; + let store; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } + }) + ], + declarations: [LogoutComponent], + providers: [ + { + provide: Store, + useValue: { + dispatch: jasmine.createSpy('dispatch') + } + } + ] + }); + + store = TestBed.get(Store); + fixture = TestBed.createComponent(LogoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should reset selected nodes from store', () => { + component.onLogoutEvent(); + + expect(store.dispatch).toHaveBeenCalledWith(new SetSelectedNodesAction([])); + }); +}); diff --git a/src/app/components/common/logout/logout.component.ts b/src/app/components/common/logout/logout.component.ts new file mode 100644 index 000000000..619193a3b --- /dev/null +++ b/src/app/components/common/logout/logout.component.ts @@ -0,0 +1,44 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2019 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 } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppStore, SetSelectedNodesAction } from '@alfresco/aca-shared/store'; + +@Component({ + selector: 'aca-logout', + template: ` + + ` +}) +export class LogoutComponent { + constructor(private store: Store) {} + + onLogoutEvent() { + this.store.dispatch(new SetSelectedNodesAction([])); + } +} diff --git a/src/app/components/current-user/current-user.component.html b/src/app/components/current-user/current-user.component.html index a823127cb..87092e647 100644 --- a/src/app/components/current-user/current-user.component.html +++ b/src/app/components/current-user/current-user.component.html @@ -13,21 +13,10 @@ - - - - + + - - - - diff --git a/src/app/components/current-user/current-user.component.spec.ts b/src/app/components/current-user/current-user.component.spec.ts index cfb33145b..baef2170d 100644 --- a/src/app/components/current-user/current-user.component.spec.ts +++ b/src/app/components/current-user/current-user.component.spec.ts @@ -24,9 +24,91 @@ */ import { CurrentUserComponent } from './current-user.component'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { AppExtensionService } from '../../extensions/extension.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { + AppState, + SetUserProfileAction, + SetLanguagePickerAction +} from '@alfresco/aca-shared/store'; describe('CurrentUserComponent', () => { - it('should be defined', () => { - expect(CurrentUserComponent).toBeDefined(); + let fixture: ComponentFixture; + let component: CurrentUserComponent; + let appExtensionService; + let store: Store; + const person = { + entry: { + id: 'user-id', + firstName: 'Test', + lastName: 'User', + email: 'user@email.com', + enabled: true, + isAdmin: false, + userName: 'user-name' + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AppTestingModule], + declarations: [CurrentUserComponent], + providers: [AppExtensionService], + schemas: [NO_ERRORS_SCHEMA] + }); + + fixture = TestBed.createComponent(CurrentUserComponent); + appExtensionService = TestBed.get(AppExtensionService); + store = TestBed.get(Store); + component = fixture.componentInstance; + }); + + it('should get profile data', done => { + const expectedProfile = { + firstName: 'Test', + lastName: 'User', + userName: 'Test User', + isAdmin: true, + id: 'user-id', + groups: [] + }; + + fixture.detectChanges(); + + store.dispatch( + new SetUserProfileAction({ person: person.entry, groups: [] }) + ); + + component.profile$.subscribe((profile: any) => { + expect(profile).toEqual(jasmine.objectContaining(expectedProfile)); + done(); + }); + }); + + it('should set language picker state', done => { + fixture.detectChanges(); + + store.dispatch(new SetLanguagePickerAction(true)); + + component.languagePicker$.subscribe((languagePicker: boolean) => { + expect(languagePicker).toBe(true); + done(); + }); + }); + + it('should set menu actions', () => { + const actions = [ + { + id: 'action-id' + } + ]; + spyOn(appExtensionService, 'getUserActions').and.returnValue(actions); + + fixture.detectChanges(); + + expect(component.actions).toBe(actions); }); }); diff --git a/src/app/components/current-user/current-user.component.ts b/src/app/components/current-user/current-user.component.ts index 8016bc6a6..6ccb79e58 100644 --- a/src/app/components/current-user/current-user.component.ts +++ b/src/app/components/current-user/current-user.component.ts @@ -23,17 +23,16 @@ * along with Alfresco. If not, see . */ -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { ProfileState } from '@alfresco/adf-extensions'; +import { ProfileState, ContentActionRef } from '@alfresco/adf-extensions'; import { AppStore, - SetSelectedNodesAction, getUserProfile, getLanguagePickerState } from '@alfresco/aca-shared/store'; -import { AppService } from '@alfresco/aca-shared'; +import { AppExtensionService } from '../../extensions/extension.service'; @Component({ selector: 'aca-current-user', @@ -41,20 +40,23 @@ import { AppService } from '@alfresco/aca-shared'; encapsulation: ViewEncapsulation.None, host: { class: 'aca-current-user' } }) -export class CurrentUserComponent { +export class CurrentUserComponent implements OnInit { profile$: Observable; languagePicker$: Observable; + actions: Array = []; - get showLogout(): boolean { - return !this.appService.withCredentials; - } + constructor( + private store: Store, + private extensions: AppExtensionService + ) {} - constructor(private store: Store, private appService: AppService) { + ngOnInit() { this.profile$ = this.store.select(getUserProfile); - this.languagePicker$ = store.select(getLanguagePickerState); + this.languagePicker$ = this.store.select(getLanguagePickerState); + this.actions = this.extensions.getUserActions(); } - onLogoutEvent() { - this.store.dispatch(new SetSelectedNodesAction([])); + trackByActionId(_: number, action: ContentActionRef) { + return action.id; } } diff --git a/src/app/components/current-user/current-user.module.ts b/src/app/components/current-user/current-user.module.ts index 2fa14bc3b..77f47bc8b 100644 --- a/src/app/components/current-user/current-user.module.ts +++ b/src/app/components/current-user/current-user.module.ts @@ -26,12 +26,19 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CoreModule } from '@alfresco/adf-core'; +import { ExtensionsModule } from '@alfresco/adf-extensions'; import { CurrentUserComponent } from './current-user.component'; +import { UserMenuItemComponent } from './user-menu-item.component'; import { RouterModule } from '@angular/router'; @NgModule({ - imports: [CommonModule, CoreModule.forChild(), RouterModule], - declarations: [CurrentUserComponent], - exports: [CurrentUserComponent] + imports: [ + CommonModule, + CoreModule.forChild(), + RouterModule, + ExtensionsModule + ], + declarations: [CurrentUserComponent, UserMenuItemComponent], + exports: [CurrentUserComponent, UserMenuItemComponent] }) export class AppCurrentUserModule {} diff --git a/src/app/components/current-user/user-menu-item.component.html b/src/app/components/current-user/user-menu-item.component.html new file mode 100644 index 000000000..a6209b143 --- /dev/null +++ b/src/app/components/current-user/user-menu-item.component.html @@ -0,0 +1,36 @@ + diff --git a/src/app/components/current-user/user-menu-item.component.spec.ts b/src/app/components/current-user/user-menu-item.component.spec.ts new file mode 100644 index 000000000..7605d3c71 --- /dev/null +++ b/src/app/components/current-user/user-menu-item.component.spec.ts @@ -0,0 +1,126 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2019 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 { TestBed, ComponentFixture } from '@angular/core/testing'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { AppExtensionService } from '../../extensions/extension.service'; +import { UserMenuItemComponent } from './user-menu-item.component'; +import { + TranslateModule, + TranslateLoader, + TranslateFakeLoader +} from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ContentActionRef } from '@alfresco/adf-extensions'; + +describe('UserMenuItemComponent', () => { + let fixture: ComponentFixture; + let component: UserMenuItemComponent; + let appExtensionService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + AppTestingModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } + }) + ], + declarations: [UserMenuItemComponent], + providers: [AppExtensionService], + schemas: [NO_ERRORS_SCHEMA] + }); + + fixture = TestBed.createComponent(UserMenuItemComponent); + appExtensionService = TestBed.get(AppExtensionService); + component = fixture.componentInstance; + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should render button action', () => { + component.actionRef = { + id: 'action-button', + title: 'Test Button', + actions: { + click: 'TEST_EVENT' + } + } as ContentActionRef; + fixture.detectChanges(); + + const buttonElement = fixture.nativeElement.querySelector('#action-button'); + expect(buttonElement).not.toBe(null); + }); + + it('should render menu action', () => { + component.actionRef = { + type: 'menu', + id: 'action-menu', + title: 'Test Button', + actions: { + click: 'TEST_EVENT' + } + } as ContentActionRef; + fixture.detectChanges(); + + const menuElement = fixture.nativeElement.querySelector('#action-menu'); + expect(menuElement).not.toBe(null); + }); + + it('should render custom action', () => { + component.actionRef = { + type: 'custom', + id: 'action-custom', + component: 'custom-component' + } as ContentActionRef; + fixture.detectChanges(); + + const componentElement = fixture.nativeElement.querySelector( + '#custom-component' + ); + expect(componentElement).not.toBe(null); + }); + + it('should run defined action', () => { + spyOn(appExtensionService, 'runActionById'); + + component.actionRef = { + id: 'action-button', + title: 'Test Button', + actions: { + click: 'TEST_EVENT' + } + } as ContentActionRef; + fixture.detectChanges(); + + const buttonElement = fixture.nativeElement.querySelector('#action-button'); + buttonElement.dispatchEvent(new MouseEvent('click')); + expect(appExtensionService.runActionById).toHaveBeenCalledWith( + 'TEST_EVENT' + ); + }); +}); diff --git a/src/app/components/current-user/user-menu-item.component.ts b/src/app/components/current-user/user-menu-item.component.ts new file mode 100644 index 000000000..ea5e2c3c0 --- /dev/null +++ b/src/app/components/current-user/user-menu-item.component.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2019 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, ViewEncapsulation } from '@angular/core'; +import { ContentActionRef } from '@alfresco/adf-extensions'; +import { AppExtensionService } from '../../extensions/extension.service'; + +@Component({ + selector: 'app-user-menu-item', + templateUrl: 'user-menu-item.component.html', + encapsulation: ViewEncapsulation.None, + host: { class: 'app-user-menu-item' } +}) +export class UserMenuItemComponent { + @Input() + actionRef: ContentActionRef; + + constructor(private extensions: AppExtensionService) {} + + runAction() { + if (this.hasClickAction(this.actionRef)) { + this.extensions.runActionById(this.actionRef.actions.click); + } + } + + private hasClickAction(actionRef: ContentActionRef): boolean { + if (actionRef && actionRef.actions && actionRef.actions.click) { + return true; + } + return false; + } + + trackById(_: number, obj: { id: string }) { + return obj.id; + } +} diff --git a/src/app/extensions/core.extensions.module.ts b/src/app/extensions/core.extensions.module.ts index 60d317cc2..54cfca4f1 100644 --- a/src/app/extensions/core.extensions.module.ts +++ b/src/app/extensions/core.extensions.module.ts @@ -51,6 +51,8 @@ import { } from '@alfresco/adf-content-services'; import { ToggleSharedComponent } from '../components/common/toggle-shared/toggle-shared.component'; import { ViewNodeComponent } from '../components/toolbar/view-node/view-node.component'; +import { LanguagePickerComponent } from '../components/common/language-picker/language-picker.component'; +import { LogoutComponent } from '../components/common/logout/logout.component'; export function setupExtensions(service: AppExtensionService): Function { return () => service.load(); @@ -101,7 +103,9 @@ export class CoreExtensionsModule { 'app.columns.trashcanName': TrashcanNameColumnComponent, 'app.columns.location': LocationLinkComponent, 'app.toolbar.toggleEditOffline': ToggleEditOfflineComponent, - 'app.toolbar.viewNode': ViewNodeComponent + 'app.toolbar.viewNode': ViewNodeComponent, + 'app.languagePicker': LanguagePickerComponent, + 'app.logout': LogoutComponent }); extensions.setAuthGuards({ @@ -166,7 +170,9 @@ export class CoreExtensionsModule { 'app.navigation.isSharedFileViewer': rules.isSharedFileViewer, 'repository.isQuickShareEnabled': rules.hasQuickShareEnabled, - 'user.isAdmin': rules.isAdmin + 'user.isAdmin': rules.isAdmin, + 'app.canShowLanguagePicker': rules.canShowLanguagePicker, + 'app.canShowLogout': rules.canShowLogout }); } } diff --git a/src/app/extensions/extension.service.spec.ts b/src/app/extensions/extension.service.spec.ts index 33748a2dc..fabafb767 100644 --- a/src/app/extensions/extension.service.spec.ts +++ b/src/app/extensions/extension.service.spec.ts @@ -39,18 +39,21 @@ import { ExtensionConfig, ComponentRegisterService } from '@alfresco/adf-extensions'; +import { AppConfigService } from '@alfresco/adf-core'; describe('AppExtensionService', () => { let service: AppExtensionService; let store: Store; let extensions: ExtensionService; let components: ComponentRegisterService; + let appConfigService: AppConfigService; beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule] }); + appConfigService = TestBed.get(AppConfigService); store = TestBed.get(Store); service = TestBed.get(AppExtensionService); extensions = TestBed.get(ExtensionService); @@ -784,4 +787,114 @@ describe('AppExtensionService', () => { expect(service.getSharedLinkViewerToolbarActions()).toEqual(actions); }); }); + + describe('withCredentials', () => { + it('should set `withCredentials` to true from app configuration', () => { + appConfigService.config = { + auth: { withCredentials: true } + }; + applyConfig({ + $id: 'test', + $name: 'test', + $version: '1.0.0', + $license: 'MIT', + $vendor: 'Good company', + $runtime: '1.5.0' + }); + + expect(service.withCredentials).toBe(true); + }); + + it('should set `withCredentials` to false from app configuration', () => { + appConfigService.config = { + auth: { withCredentials: false } + }; + applyConfig({ + $id: 'test', + $name: 'test', + $version: '1.0.0', + $license: 'MIT', + $vendor: 'Good company', + $runtime: '1.5.0' + }); + + expect(service.withCredentials).toBe(false); + }); + + it('should set `withCredentials` to false as default value if no app configuration', () => { + appConfigService.config = {}; + applyConfig({ + $id: 'test', + $name: 'test', + $version: '1.0.0', + $license: 'MIT', + $vendor: 'Good company', + $runtime: '1.5.0' + }); + + expect(service.withCredentials).toBe(false); + }); + }); + + describe('userActions', () => { + it('should load user actions from the config', () => { + applyConfig({ + $id: 'test', + $name: 'test', + $version: '1.0.0', + $license: 'MIT', + $vendor: 'Good company', + $runtime: '1.5.0', + features: { + userActions: [ + { + id: 'aca:toolbar/separator-1', + order: 1, + type: ContentActionType.separator, + title: 'action1' + }, + { + id: 'aca:toolbar/separator-2', + order: 2, + type: ContentActionType.separator, + title: 'action2' + } + ] + } + }); + + expect(service.userActions.length).toBe(2); + }); + + it('should sort user actions by order', () => { + applyConfig({ + $id: 'test', + $name: 'test', + $version: '1.0.0', + $license: 'MIT', + $vendor: 'Good company', + $runtime: '1.5.0', + features: { + userActions: [ + { + id: 'aca:toolbar/separator-2', + order: 2, + type: ContentActionType.separator, + title: 'action2' + }, + { + id: 'aca:toolbar/separator-1', + order: 1, + type: ContentActionType.separator, + title: 'action1' + } + ] + } + }); + + expect(service.userActions.length).toBe(2); + expect(service.userActions[0].id).toBe('aca:toolbar/separator-1'); + expect(service.userActions[1].id).toBe('aca:toolbar/separator-2'); + }); + }); }); diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index 89937256c..363ef3a48 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -28,7 +28,11 @@ import { Store } from '@ngrx/store'; import { Route } from '@angular/router'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; -import { AppStore, getRuleContext } from '@alfresco/aca-shared/store'; +import { + AppStore, + getRuleContext, + getLanguagePickerState +} from '@alfresco/aca-shared/store'; import { NodePermissionService } from '@alfresco/aca-shared'; import { SelectionState, @@ -78,6 +82,7 @@ export class AppExtensionService implements RuleContext { sidebar: Array = []; contentMetadata: any; viewerRules: ViewerRules = {}; + userActions: Array = []; documentListPresets: { files: Array; @@ -103,6 +108,8 @@ export class AppExtensionService implements RuleContext { navigation: NavigationState; profile: ProfileState; repository: RepositoryInfo; + withCredentials: boolean; + languagePicker: boolean; references$: Observable; @@ -124,6 +131,10 @@ export class AppExtensionService implements RuleContext { this.profile = result.profile; this.repository = result.repository; }); + + this.store.select(getLanguagePickerState).subscribe(result => { + this.languagePicker = result; + }); } async load() { @@ -170,6 +181,10 @@ export class AppExtensionService implements RuleContext { config, 'features.sidebar' ); + this.userActions = this.loader.getContentActions( + config, + 'features.userActions' + ); this.contentMetadata = this.loadContentMetadata(config); this.documentListPresets = { @@ -186,6 +201,11 @@ export class AppExtensionService implements RuleContext { searchLibraries: this.getDocumentListPreset(config, 'search-libraries') }; + this.withCredentials = this.appConfig.get( + 'auth.withCredentials', + false + ); + if (config.features && config.features.viewer) { this.viewerRules = (config.features.viewer['rules'] || {}); } @@ -473,6 +493,12 @@ export class AppExtensionService implements RuleContext { return this.getAllowedActions(this.contextMenuActions); } + getUserActions(): Array { + return this.userActions + .filter(action => this.filterVisible(action)) + .sort(sortByOrder); + } + copyAction(action: ContentActionRef): ContentActionRef { return { ...action, diff --git a/src/assets/plugins/app.header.json b/src/assets/plugins/app.header.json index 8e582c131..5ad36ae22 100644 --- a/src/assets/plugins/app.header.json +++ b/src/assets/plugins/app.header.json @@ -22,6 +22,26 @@ ], "features": { + "userActions": [ + { + "id": "app.languagePicker", + "order": 100, + "type": "custom", + "component": "app.languagePicker", + "rules": { + "visible": "app.canShowLanguagePicker" + } + }, + { + "id": "app.logout", + "order": 200, + "type": "custom", + "component": "app.logout", + "rules": { + "visible": "app.canShowLogout" + } + } + ], "header": [ { "id": "app.header.more",