[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
This commit is contained in:
Bartosz Sekuła 2021-11-22 08:23:10 +01:00 committed by GitHub
parent e9d5ab753a
commit 60446dc36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 380 additions and 13 deletions

View File

@ -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`).

View File

@ -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",

View File

@ -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<Array<ContentActionRef>>([]);
private _openWithActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _createActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _mainActions = new BehaviorSubject<ContentActionRef>(null);
private _sidebarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
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<ContentActionRef>(config, 'features.create'));
this._mainActions.next(this.loader.getFeatures(config).mainAction);
this.navbar = this.loadNavBar(config);
this.sidebarTabs = this.loader.getElements<SidebarTabRef>(config, 'features.sidebar.tabs');
@ -364,6 +366,17 @@ export class AppExtensionService implements RuleContext {
);
}
getMainAction(): Observable<ContentActionRef> {
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));

View File

@ -1,5 +1,13 @@
<ng-container *ngIf="expanded">
<button data-automation-id="create-button" mat-raised-button [matMenuTriggerFor]="rootMenu" title="{{ 'APP.NEW_MENU.TOOLTIP' | translate }}">
<button
mat-stroked-button
data-automation-id="create-button"
[matMenuTriggerFor]="rootMenu"
title="{{ 'APP.NEW_MENU.TOOLTIP' | translate }}"
[class]="{
'app-create-menu-secondary-button': isMainActionPresent
}"
>
<span class="app-create-menu__text">{{ 'APP.NEW_MENU.LABEL' | translate }}</span>
<mat-icon title="{{ 'APP.NEW_MENU.TOOLTIP' | translate }}">arrow_drop_down</mat-icon>
</button>
@ -8,7 +16,10 @@
<ng-container *ngIf="!expanded">
<button
mat-icon-button
class="app-create-menu--collapsed"
[class]="{
'app-create-menu-secondary-button': isMainActionPresent,
'app-create-menu--collapsed': true
}"
data-automation-id="create-button"
[matMenuTriggerFor]="rootMenu"
#createMenu="matMenuTrigger"

View File

@ -5,22 +5,28 @@
.app-create-menu {
width: 100%;
.mat-raised-button {
width: 100%;
display: block;
box-shadow: none !important;
height: 37.5px;
.mat-stroked-button:not(.app-create-menu-secondary-button) {
background-color: var(--theme-accent-color);
color: var(--theme-primary-color-default-contrast);
margin-top: 0px;
.mat-icon {
color: var(--theme-primary-color-default-contrast);
}
}
.mat-stroked-button {
width: 100%;
height: 37.5px;
border-radius: 4px;
font-size: 12.7px;
font-weight: normal;
text-transform: uppercase;
margin-top: 10px;
.mat-icon {
width: 24px;
height: 25px;
color: var(--theme-primary-color-default-contrast);
}
&__text {

View File

@ -35,6 +35,7 @@ import { AppTestingModule } from '../../testing/app-testing.module';
import { MatMenuModule } from '@angular/material/menu';
import { MatButtonModule } from '@angular/material/button';
import { of } from 'rxjs';
import { getContentActionRef } from '../../testing/content-action-ref';
describe('CreateMenuComponent', () => {
let fixture: ComponentFixture<CreateMenuComponent>;
@ -58,6 +59,8 @@ describe('CreateMenuComponent', () => {
])
);
spyOn(extensionService, 'getMainAction').and.returnValue(getContentActionRef());
fixture = TestBed.createComponent(CreateMenuComponent);
});
@ -132,4 +135,14 @@ describe('CreateMenuComponent', () => {
expect(level2).not.toBeNull();
});
it('should recognise if main button is present', async () => {
fixture.detectChanges();
await fixture.whenStable();
const button: HTMLButtonElement = fixture.debugElement.query(By.css('[data-automation-id="create-button"]')).nativeElement;
const isSecondaryButton = button.classList.contains('app-create-menu-secondary-button');
expect(isSecondaryButton).toBeTrue();
});
});

View File

@ -39,6 +39,7 @@ import { AppExtensionService } from '@alfresco/aca-shared';
export class CreateMenuComponent implements OnInit, OnDestroy {
createActions: Array<ContentActionRef> = [];
onDestroy$: Subject<boolean> = new Subject<boolean>();
isMainActionPresent: boolean;
@Input()
showLabel: boolean;
@ -55,6 +56,13 @@ export class CreateMenuComponent implements OnInit, OnDestroy {
.subscribe((createActions) => {
this.createActions = createActions;
});
this.extensions
.getMainAction()
.pipe(takeUntil(this.onDestroy$))
.subscribe((mainAction) => {
this.isMainActionPresent = !!mainAction;
});
}
ngOnDestroy() {

View File

@ -0,0 +1,13 @@
<ng-container *ngIf="(mainAction$ | async) as action">
<ng-container [ngSwitch]="action.type">
<button
*ngSwitchCase="actionTypes.button"
mat-stroked-button
(click)="runAction(action.actions.click)"
class="app-main-action-button"
>
{{action.title | translate}}
</button>
</ng-container>
</ng-container>

View File

@ -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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<MainActionComponent>;
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);
});
});

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ContentActionRef>;
actionTypes = ContentActionType;
private onDestroy$ = new Subject<boolean>();
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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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 {}

View File

@ -1,6 +1,8 @@
<div class="sidenav">
<ng-container [ngSwitch]="mode">
<div class="section action-menu" [ngClass]="'section--' + mode">
<app-main-action></app-main-action>
<app-create-menu [expanded]="mode === 'expanded'"></app-create-menu>
</div>

View File

@ -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 {

View File

@ -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,

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import { ContentActionRef } from '@alfresco/adf-extensions';
import { Observable } from 'rxjs';
import { getContentActionRef } from './content-action-ref';
export class AppExtensionServiceMock {
getMainAction(): Observable<ContentActionRef> {
return getContentActionRef();
}
runActionById(_id: string) {}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ContentActionRef> => {
return of({
id: 'id',
type: ContentActionType.button,
title: ACTION_TITLE,
actions: {
click: ACTION_CLICK
}
});
};