From 60446dc36e28188019eff80e2c3a5fe9eaa4f98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Seku=C5=82a?= Date: Mon, 22 Nov 2021 08:23:10 +0100 Subject: [PATCH] [AAE-6309] New process start button (#2353) * [AEE-6309] New start process button * [AEE-6309] Added documentation * [AEE-6309] Change main action to object * [AEE-6309] Clean code * [AEE-6309] Linting fix * [AEE-6309] Fix trashcan translation * [AEE-6309] Fix tests --- docs/extending/application-features.md | 28 ++++++- extension.schema.json | 5 ++ .../src/lib/services/app.extension.service.ts | 15 +++- .../create-menu/create-menu.component.html | 15 +++- .../create-menu/create-menu.component.scss | 18 ++-- .../create-menu/create-menu.component.spec.ts | 13 +++ .../create-menu/create-menu.component.ts | 8 ++ .../main-action/main-action.component.html | 13 +++ .../main-action/main-action.component.scss | 6 ++ .../main-action/main-action.component.spec.ts | 82 +++++++++++++++++++ .../main-action/main-action.component.ts | 59 +++++++++++++ .../main-action/main-action.module.ts | 37 +++++++++ .../components/sidenav/sidenav.component.html | 2 + .../components/sidenav/sidenav.component.scss | 4 +- src/app/components/sidenav/sidenav.module.ts | 12 ++- src/app/testing/app-extension-service-mock.ts | 35 ++++++++ src/app/testing/content-action-ref.ts | 41 ++++++++++ 17 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 src/app/components/main-action/main-action.component.html create mode 100644 src/app/components/main-action/main-action.component.scss create mode 100644 src/app/components/main-action/main-action.component.spec.ts create mode 100644 src/app/components/main-action/main-action.component.ts create mode 100644 src/app/components/main-action/main-action.module.ts create mode 100644 src/app/testing/app-extension-service-mock.ts create mode 100644 src/app/testing/content-action-ref.ts diff --git a/docs/extending/application-features.md b/docs/extending/application-features.md index f3a427511..af56a20ef 100644 --- a/docs/extending/application-features.md +++ b/docs/extending/application-features.md @@ -15,7 +15,7 @@ The ACA supports the following set of extension points: - Viewer - Sidebar (aka Info Drawer) - Content metadata presets (for `Properties` tab) -- File list layout +- File list layout - Search All the customizations are stored in the `features` section of the configuration file: @@ -121,6 +121,32 @@ Please refer to the [Content Actions](#content-actions) section for more details **Tip:** It is also possible to update or disable existing entries from within the external extension files. You will need to know the `id` of the target element to customize. +## Main action + +Add possibility to show application `Main Action` button. The action is going to be shown above the `New` button, additionally `Main Action` will be highlighted as primary button, and `New` as secondary. + +```json +{ + "$schema": "../../../extension.schema.json", + "$version": "1.0.0", + "$name": "plugin1", + + "features": { + "mainAction": { + "id": "plugin1.id", + "type": "button", + "title": "Create", + "actions": { + "click": "MAIN_ACTION_CALL" + }, + "rules": { + "enabled": "app.navigation.canCall" + } + } + } +} +``` + ## Navigation Bar The Navigation bar consists of Link elements (`NavBarLinkRef`) organized into Groups (`NavBarGroupRef`). diff --git a/extension.schema.json b/extension.schema.json index c34b8a4a4..cc244ffc8 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -800,6 +800,11 @@ "items": { "$ref": "#/definitions/contentActionRef" }, "minItems": 1 }, + "mainAction": { + "description": "Main actions displayed on every page", + "type": "object", + "$ref": "#/definitions/contentActionRef" + }, "viewer": { "description": "Viewer component extensions", "type": "object", diff --git a/projects/aca-shared/src/lib/services/app.extension.service.ts b/projects/aca-shared/src/lib/services/app.extension.service.ts index a0fcdf2a8..5ef80531e 100644 --- a/projects/aca-shared/src/lib/services/app.extension.service.ts +++ b/projects/aca-shared/src/lib/services/app.extension.service.ts @@ -56,7 +56,7 @@ import { RepositoryInfo, NodeEntry } from '@alfresco/js-api'; import { ViewerRules } from '../models/viewer.rules'; import { SettingsGroupRef } from '../models/types'; import { NodePermissionService } from '../services/node-permission.service'; -import { map } from 'rxjs/operators'; +import { filter, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -78,6 +78,7 @@ export class AppExtensionService implements RuleContext { private _contextMenuActions = new BehaviorSubject>([]); private _openWithActions = new BehaviorSubject>([]); private _createActions = new BehaviorSubject>([]); + private _mainActions = new BehaviorSubject(null); private _sidebarActions = new BehaviorSubject>([]); documentListPresets: { @@ -156,6 +157,7 @@ export class AppExtensionService implements RuleContext { this._contextMenuActions.next(this.loader.getContentActions(config, 'features.contextMenu')); this._openWithActions.next(this.loader.getContentActions(config, 'features.viewer.openWith')); this._createActions.next(this.loader.getElements(config, 'features.create')); + this._mainActions.next(this.loader.getFeatures(config).mainAction); this.navbar = this.loadNavBar(config); this.sidebarTabs = this.loader.getElements(config, 'features.sidebar.tabs'); @@ -364,6 +366,17 @@ export class AppExtensionService implements RuleContext { ); } + getMainAction(): Observable { + return this._mainActions.pipe( + filter((mainAction) => mainAction && this.filterVisible(mainAction)), + map((mainAction) => { + let actionCopy = this.copyAction(mainAction); + actionCopy = this.setActionDisabledFromRule(actionCopy); + return actionCopy; + }) + ); + } + private buildMenu(actionRef: ContentActionRef): ContentActionRef { if (actionRef.type === ContentActionType.menu && actionRef.children && actionRef.children.length > 0) { const children = actionRef.children.filter((action) => this.filterVisible(action)).map((action) => this.buildMenu(action)); diff --git a/src/app/components/create-menu/create-menu.component.html b/src/app/components/create-menu/create-menu.component.html index ef0aa9994..94c39ba58 100644 --- a/src/app/components/create-menu/create-menu.component.html +++ b/src/app/components/create-menu/create-menu.component.html @@ -1,5 +1,13 @@ - @@ -8,7 +16,10 @@ + + + diff --git a/src/app/components/main-action/main-action.component.scss b/src/app/components/main-action/main-action.component.scss new file mode 100644 index 000000000..380e9fbef --- /dev/null +++ b/src/app/components/main-action/main-action.component.scss @@ -0,0 +1,6 @@ +.app-main-action-button { + width: 100%; + border-radius: 4px; + background-color: var(--theme-accent-color); + color: var(--theme-primary-color-default-contrast); +} diff --git a/src/app/components/main-action/main-action.component.spec.ts b/src/app/components/main-action/main-action.component.spec.ts new file mode 100644 index 000000000..857eca9a0 --- /dev/null +++ b/src/app/components/main-action/main-action.component.spec.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MainActionComponent } from './main-action.component'; +import { TranslationService, TranslationMock } from '@alfresco/adf-core'; +import { AppExtensionService } from '@alfresco/aca-shared'; +import { of } from 'rxjs'; +import { ACTION_CLICK, ACTION_TITLE } from '../../testing/content-action-ref'; +import { AppExtensionServiceMock } from '../../testing/app-extension-service-mock'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('MainActionComponent', () => { + let startProcessButtonComponent: MainActionComponent; + let fixture: ComponentFixture; + let appExtensionService: AppExtensionServiceMock; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [CommonModule, MatButtonModule, TranslateModule.forRoot()], + providers: [ + { provide: TranslationService, useClass: TranslationMock }, + { provide: AppExtensionService, useClass: AppExtensionServiceMock } + ] + }).compileComponents(); + + appExtensionService = TestBed.inject(AppExtensionService); + + fixture = TestBed.createComponent(MainActionComponent); + startProcessButtonComponent = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should display button if main action is configured', () => { + const button = fixture.debugElement.nativeElement.querySelector('.app-main-action-button'); + expect(button).toBeTruthy(); + expect(button.textContent.trim()).toBe(ACTION_TITLE); + }); + + it('should not display button if main action is not configured', () => { + spyOn(appExtensionService, 'getMainAction').and.returnValue(of(undefined)); + startProcessButtonComponent.ngOnInit(); + fixture.detectChanges(); + + const button = fixture.debugElement.nativeElement.querySelector('.app-main-action-button'); + expect(button).toBeFalsy(); + }); + + it('should call extension action', () => { + const runExtensionActionSpy = spyOn(appExtensionService, 'runActionById'); + + const button = fixture.debugElement.nativeElement.querySelector('button'); + button.click(); + + expect(runExtensionActionSpy).toHaveBeenCalledWith(ACTION_CLICK); + }); +}); diff --git a/src/app/components/main-action/main-action.component.ts b/src/app/components/main-action/main-action.component.ts new file mode 100644 index 000000000..412b676f6 --- /dev/null +++ b/src/app/components/main-action/main-action.component.ts @@ -0,0 +1,59 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 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 { AppExtensionService } from '@alfresco/aca-shared'; +import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +export const START_PROCESS_ACTION_ID = 'alfresco.app.start.process'; + +@Component({ + selector: 'app-main-action', + templateUrl: './main-action.component.html', + styleUrls: ['./main-action.component.scss'] +}) +export class MainActionComponent implements OnInit, OnDestroy { + mainAction$: Observable; + + actionTypes = ContentActionType; + + private onDestroy$ = new Subject(); + + constructor(private extensions: AppExtensionService) {} + + ngOnDestroy(): void { + this.onDestroy$.next(true); + } + + ngOnInit(): void { + this.mainAction$ = this.extensions.getMainAction().pipe(takeUntil(this.onDestroy$)); + } + + runAction(action: string): void { + this.extensions.runActionById(action); + } +} diff --git a/src/app/components/main-action/main-action.module.ts b/src/app/components/main-action/main-action.module.ts new file mode 100644 index 000000000..335d228a9 --- /dev/null +++ b/src/app/components/main-action/main-action.module.ts @@ -0,0 +1,37 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslateModule } from '@ngx-translate/core'; +import { MainActionComponent } from './main-action.component'; + +@NgModule({ + imports: [CommonModule, MatButtonModule, TranslateModule.forChild()], + exports: [MainActionComponent], + declarations: [MainActionComponent] +}) +export class MainActionModule {} diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index e179af7f7..70f6cb594 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -1,6 +1,8 @@
+ +
diff --git a/src/app/components/sidenav/sidenav.component.scss b/src/app/components/sidenav/sidenav.component.scss index 618894bdf..03cd9fa71 100644 --- a/src/app/components/sidenav/sidenav.component.scss +++ b/src/app/components/sidenav/sidenav.component.scss @@ -33,9 +33,9 @@ .action-menu { display: flex; - height: 40px; + flex-direction: column; justify-content: center; - align-items: center; + align-items: stretch; } .section.action-menu { diff --git a/src/app/components/sidenav/sidenav.module.ts b/src/app/components/sidenav/sidenav.module.ts index eb69b7742..52105760f 100644 --- a/src/app/components/sidenav/sidenav.module.ts +++ b/src/app/components/sidenav/sidenav.module.ts @@ -37,8 +37,18 @@ import { ActiveLinkDirective } from './directives/active-link.directive'; import { ExpandMenuComponent } from './components/expand-menu.component'; import { ButtonMenuComponent } from './components/button-menu.component'; import { ActionDirective } from './directives/action.directive'; +import { MainActionModule } from '../main-action/main-action.module'; + @NgModule({ - imports: [CommonModule, CoreModule.forChild(), CoreExtensionsModule.forChild(), ExtensionsModule.forChild(), RouterModule, AppCreateMenuModule], + imports: [ + CommonModule, + CoreModule.forChild(), + CoreExtensionsModule.forChild(), + ExtensionsModule.forChild(), + RouterModule, + AppCreateMenuModule, + MainActionModule + ], declarations: [ MenuPanelDirective, ExpansionPanelDirective, diff --git a/src/app/testing/app-extension-service-mock.ts b/src/app/testing/app-extension-service-mock.ts new file mode 100644 index 000000000..eefa1b4e3 --- /dev/null +++ b/src/app/testing/app-extension-service-mock.ts @@ -0,0 +1,35 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 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 { ContentActionRef } from '@alfresco/adf-extensions'; +import { Observable } from 'rxjs'; +import { getContentActionRef } from './content-action-ref'; +export class AppExtensionServiceMock { + getMainAction(): Observable { + return getContentActionRef(); + } + + runActionById(_id: string) {} +} diff --git a/src/app/testing/content-action-ref.ts b/src/app/testing/content-action-ref.ts new file mode 100644 index 000000000..527ef89a6 --- /dev/null +++ b/src/app/testing/content-action-ref.ts @@ -0,0 +1,41 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 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 { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions'; +import { Observable, of } from 'rxjs'; + +export const ACTION_TITLE = 'ACTION_TITLE'; +export const ACTION_CLICK = 'ACTION_CLICK'; + +export const getContentActionRef = (): Observable => { + return of({ + id: 'id', + type: ContentActionType.button, + title: ACTION_TITLE, + actions: { + click: ACTION_CLICK + } + }); +};