mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACA] Context menu - support nested sub menus (#715)
* remove ContextMenuItemDirective * remove custom menu animation * remove custom menu styling * menu item component * material menu trigger * clean up menu theme * update context menu module * remoe context menu from libraries document list * clean up * clean up * tests
This commit is contained in:
committed by
Denys Vuika
parent
9dcdacce40
commit
4802656d79
@@ -1,68 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
query,
|
||||
group,
|
||||
sequence
|
||||
} from '@angular/animations';
|
||||
|
||||
export const contextMenuAnimation = [
|
||||
state(
|
||||
'void',
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'scale(0.01, 0.01)'
|
||||
})
|
||||
),
|
||||
transition(
|
||||
'void => *',
|
||||
sequence([
|
||||
query('.mat-menu-content', style({ opacity: 0 })),
|
||||
animate(
|
||||
'100ms linear',
|
||||
style({ opacity: 1, transform: 'scale(1, 0.5)' })
|
||||
),
|
||||
group([
|
||||
query(
|
||||
'.mat-menu-content',
|
||||
animate(
|
||||
'400ms cubic-bezier(0.55, 0, 0.55, 0.2)',
|
||||
style({ opacity: 1 })
|
||||
)
|
||||
),
|
||||
animate(
|
||||
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
|
||||
style({ transform: 'scale(1, 1)' })
|
||||
)
|
||||
])
|
||||
])
|
||||
),
|
||||
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
|
||||
];
|
@@ -0,0 +1,39 @@
|
||||
<div class="aca-context-menu">
|
||||
<ng-container [ngSwitch]="actionRef.type">
|
||||
|
||||
<ng-container *ngSwitchCase="'menu'">
|
||||
<button
|
||||
mat-menu-item
|
||||
[id]="actionRef.id"
|
||||
[matMenuTriggerFor]="childMenu">
|
||||
<mat-icon color="primary">{{ actionRef.icon }}</mat-icon>
|
||||
<span>{{ actionRef.title | translate }}</span>
|
||||
</button>
|
||||
|
||||
<mat-menu #childMenu="matMenu">
|
||||
<ng-container *ngFor="let child of actionRef.children; trackBy: trackById">
|
||||
<app-context-menu-item [actionRef]="child"></app-context-menu-item>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'separator'">
|
||||
<mat-divider></mat-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'custom'">
|
||||
<adf-dynamic-component [id]="actionRef.component"></adf-dynamic-component>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
<button mat-menu-item
|
||||
color="primary"
|
||||
[id]="actionRef.id"
|
||||
(click)="runAction()">
|
||||
<mat-icon color="primary">{{ actionRef.icon }}</mat-icon>
|
||||
<span>{{ actionRef.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
@@ -0,0 +1,107 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { ContextMenuItemComponent } from './context-menu-item.component';
|
||||
import { ContextMenuModule } from './context-menu.module';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslateLoader,
|
||||
TranslateFakeLoader
|
||||
} from '@ngx-translate/core';
|
||||
|
||||
describe('ContextMenuComponent', () => {
|
||||
let fixture: ComponentFixture<ContextMenuItemComponent>;
|
||||
let component: ContextMenuItemComponent;
|
||||
let extensionsService;
|
||||
let contextItem;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule,
|
||||
ContextMenuModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||
})
|
||||
],
|
||||
providers: [AppExtensionService]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ContextMenuItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
extensionsService = TestBed.get(AppExtensionService);
|
||||
|
||||
contextItem = <any>{
|
||||
type: 'button',
|
||||
id: 'action-button',
|
||||
title: 'Test Button',
|
||||
actions: {
|
||||
click: 'TEST_EVENT'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should render defined menu actions items', () => {
|
||||
component.actionRef = contextItem;
|
||||
fixture.detectChanges();
|
||||
|
||||
const buttonElement = fixture.nativeElement.querySelector('button');
|
||||
expect(buttonElement.innerText.trim()).toBe(contextItem.title);
|
||||
});
|
||||
|
||||
it('should not run action when entry has no click attribute defined', () => {
|
||||
spyOn(extensionsService, 'runActionById');
|
||||
contextItem.actions = {};
|
||||
component.actionRef = contextItem;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.nativeElement
|
||||
.querySelector('#action-button')
|
||||
.dispatchEvent(new MouseEvent('click'));
|
||||
|
||||
expect(extensionsService.runActionById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should run action with provided action id', () => {
|
||||
spyOn(extensionsService, 'runActionById');
|
||||
component.actionRef = contextItem;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.nativeElement
|
||||
.querySelector('#action-button')
|
||||
.dispatchEvent(new MouseEvent('click'));
|
||||
|
||||
expect(extensionsService.runActionById).toHaveBeenCalledWith(
|
||||
contextItem.actions.click
|
||||
);
|
||||
});
|
||||
});
|
@@ -23,29 +23,36 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Directive, ElementRef, OnDestroy } from '@angular/core';
|
||||
import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
|
||||
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
||||
import { ContentActionRef } from '@alfresco/adf-extensions';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaContextMenuItem]'
|
||||
@Component({
|
||||
selector: 'app-context-menu-item',
|
||||
templateUrl: 'context-menu-item.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-context-menu-item' }
|
||||
})
|
||||
export class ContextMenuItemDirective implements OnDestroy, FocusableOption {
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private focusMonitor: FocusMonitor
|
||||
) {
|
||||
focusMonitor.monitor(this.getHostElement(), false);
|
||||
export class ContextMenuItemComponent {
|
||||
@Input()
|
||||
actionRef: ContentActionRef;
|
||||
|
||||
constructor(private extensions: AppExtensionService) {}
|
||||
|
||||
runAction() {
|
||||
if (this.hasClickAction(this.actionRef)) {
|
||||
this.extensions.runActionById(this.actionRef.actions.click);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.getHostElement());
|
||||
private hasClickAction(actionRef: ContentActionRef): boolean {
|
||||
if (actionRef && actionRef.actions && actionRef.actions.click) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
focus(origin: FocusOrigin = 'keyboard'): void {
|
||||
this.focusMonitor.focusVia(this.getHostElement(), origin);
|
||||
}
|
||||
|
||||
private getHostElement(): HTMLElement {
|
||||
return this.elementRef.nativeElement;
|
||||
trackById(index: number, obj: { id: string }) {
|
||||
return obj.id;
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||
|
||||
describe('ContextMenuItemDirective', () => {
|
||||
it('should be defined', () => {
|
||||
expect(ContextMenuItemDirective).toBeDefined();
|
||||
});
|
||||
});
|
@@ -1,48 +1,41 @@
|
||||
<div mat-menu class="mat-menu-panel"
|
||||
@panelAnimation
|
||||
<button style="visibility: hidden" [matMenuTriggerFor]="rootMenu" #rootTriggerEl></button>
|
||||
|
||||
<mat-menu #rootMenu="matMenu"
|
||||
class="aca-context-menu"
|
||||
hasBackdrop="false"
|
||||
acaContextMenuOutsideEvent
|
||||
(clickOutside)="onClickOutsideEvent()">
|
||||
|
||||
<div class="mat-menu-content">
|
||||
<ng-container *ngFor="let entry of actions" [ngSwitch]="entry.type">
|
||||
<ng-container *ngSwitchCase="'default'">
|
||||
<button mat-menu-item
|
||||
[id]="entry.id"
|
||||
acaContextMenuItem
|
||||
(click)="runAction(entry.actions.click)">
|
||||
<mat-icon color="primary">{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'button'">
|
||||
<button mat-menu-item
|
||||
[id]="entry.id"
|
||||
acaContextMenuItem
|
||||
(click)="runAction(entry.actions.click)">
|
||||
<mat-icon color="primary">{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<span *ngSwitchCase="'separator'"
|
||||
class="aca-context-menu__separator"
|
||||
[id]="entry.id">
|
||||
</span>
|
||||
|
||||
<ng-container *ngSwitchCase="'menu'">
|
||||
<button mat-menu-item
|
||||
acaContextMenuItem
|
||||
[id]="entry.id"
|
||||
class="aca-context-menu__more-actions">
|
||||
<mat-icon color="primary">{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'custom'">
|
||||
<adf-dynamic-component [id]="entry.component"></adf-dynamic-component>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let entry of actions" [ngSwitch]="entry.type">
|
||||
<ng-container *ngSwitchDefault>
|
||||
<button mat-menu-item
|
||||
[id]="entry.id"
|
||||
(click)="runAction(entry.actions.click)">
|
||||
<mat-icon color="primary">{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngSwitchCase="'separator'">
|
||||
<mat-divider></mat-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'menu'">
|
||||
<button mat-menu-item
|
||||
[id]="entry.id"
|
||||
[matMenuTriggerFor]="childMenu">
|
||||
<mat-icon color="primary">{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
|
||||
<mat-menu #childMenu="matMenu">
|
||||
<ng-container *ngFor="let child of entry.children; trackBy: trackById">
|
||||
<app-context-menu-item [actionRef]="child"></app-context-menu-item>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'custom'">
|
||||
<adf-dynamic-component [id]="entry.component"></adf-dynamic-component>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</mat-menu>
|
@@ -1,19 +0,0 @@
|
||||
.aca-context-menu {
|
||||
&__more-actions::after {
|
||||
margin-left: 34px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 0 5px 5px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
}
|
@@ -23,10 +23,104 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
TestBed,
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
tick
|
||||
} from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { ContextMenuComponent } from './context-menu.component';
|
||||
import { ContextMenuModule } from './context-menu.module';
|
||||
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslateLoader,
|
||||
TranslateFakeLoader
|
||||
} from '@ngx-translate/core';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
describe('ContextMenuComponent', () => {
|
||||
it('should be defined', () => {
|
||||
expect(ContextMenuComponent).toBeDefined();
|
||||
let fixture: ComponentFixture<ContextMenuComponent>;
|
||||
let component: ContextMenuComponent;
|
||||
let contextMenuOverlayRef;
|
||||
let extensionsService;
|
||||
const contextItem = {
|
||||
type: 'button',
|
||||
id: 'action-button',
|
||||
title: 'Test Button',
|
||||
actions: {
|
||||
click: 'TEST_EVENT'
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule,
|
||||
ContextMenuModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
AppExtensionService,
|
||||
{
|
||||
provide: ContextMenuOverlayRef,
|
||||
useValue: {
|
||||
close: jasmine.createSpy('close')
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: Store,
|
||||
useValue: {
|
||||
dispatch: () => {},
|
||||
select: () => of({ count: 1 })
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ContextMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
contextMenuOverlayRef = TestBed.get(ContextMenuOverlayRef);
|
||||
extensionsService = TestBed.get(AppExtensionService);
|
||||
|
||||
spyOn(extensionsService, 'getAllowedContextMenuActions').and.returnValue([
|
||||
contextItem
|
||||
]);
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should close context menu on Escape event', () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||||
expect(contextMenuOverlayRef.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render defined context menu actions items', fakeAsync(() => {
|
||||
component.ngAfterViewInit();
|
||||
tick(500);
|
||||
|
||||
const contextMenuElements = document.body
|
||||
.querySelector('.aca-context-menu')
|
||||
.querySelectorAll('button');
|
||||
|
||||
expect(contextMenuElements.length).toBe(1);
|
||||
expect(contextMenuElements[0].innerText).toBe(contextItem.title);
|
||||
}));
|
||||
|
||||
it('should run action with provided action id', fakeAsync(() => {
|
||||
spyOn(extensionsService, 'runActionById');
|
||||
|
||||
component.runAction(contextItem.actions.click);
|
||||
|
||||
expect(extensionsService.runActionById).toHaveBeenCalledWith(
|
||||
contextItem.actions.click
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
@@ -4,14 +4,5 @@
|
||||
|
||||
.aca-context-menu {
|
||||
@include angular-material-theme($theme);
|
||||
|
||||
&__separator {
|
||||
border-top-color: mat-color($foreground, divider);
|
||||
}
|
||||
|
||||
&__more-actions::after {
|
||||
border-color: transparent;
|
||||
border-left-color: mat-color($primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,13 +29,10 @@ import {
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
HostListener,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
AfterViewInit
|
||||
} from '@angular/core';
|
||||
import { trigger } from '@angular/animations';
|
||||
import { FocusKeyManager } from '@angular/cdk/a11y';
|
||||
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
|
||||
import { MatMenuTrigger } from '@angular/material';
|
||||
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { AppStore } from '../../store/states';
|
||||
@@ -44,42 +41,23 @@ import { Store } from '@ngrx/store';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ContentActionRef } from '@alfresco/adf-extensions';
|
||||
|
||||
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||
import { contextMenuAnimation } from './animations';
|
||||
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-context-menu',
|
||||
templateUrl: './context-menu.component.html',
|
||||
styleUrls: [
|
||||
'./context-menu.component.scss',
|
||||
'./context-menu.component.theme.scss'
|
||||
],
|
||||
styleUrls: ['./context-menu.component.theme.scss'],
|
||||
host: {
|
||||
role: 'menu',
|
||||
class: 'aca-context-menu'
|
||||
class: 'aca-context-menu-holder'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: [trigger('panelAnimation', contextMenuAnimation)]
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
private _keyManager: FocusKeyManager<ContextMenuItemDirective>;
|
||||
actions: Array<ContentActionRef> = [];
|
||||
|
||||
@ViewChildren(ContextMenuItemDirective)
|
||||
private contextMenuItems: QueryList<ContextMenuItemDirective>;
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
handleContextMenu(event: MouseEvent) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ViewChild(MatMenuTrigger)
|
||||
trigger: MatMenuTrigger;
|
||||
|
||||
@HostListener('document:keydown.Escape', ['$event'])
|
||||
handleKeydownEscape(event: KeyboardEvent) {
|
||||
@@ -90,16 +68,6 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
handleKeydownEvent(event: KeyboardEvent) {
|
||||
if (event) {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
|
||||
this._keyManager.onKeydown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private contextMenuOverlayRef: ContextMenuOverlayRef,
|
||||
private extensions: AppExtensionService,
|
||||
@@ -114,7 +82,6 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
runAction(actionId: string) {
|
||||
this.extensions.runActionById(actionId);
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -134,9 +101,6 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(
|
||||
this.contextMenuItems
|
||||
);
|
||||
this._keyManager.setFirstItemActive();
|
||||
setTimeout(() => this.trigger.openMenu(), 0);
|
||||
}
|
||||
}
|
||||
|
@@ -43,14 +43,6 @@ export class ContextActionsDirective {
|
||||
@Input('acaContextEnable')
|
||||
enabled = true;
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
if (event && this.overlayRef) {
|
||||
this.clearSelection();
|
||||
this.overlayRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
onContextMenuEvent(event: MouseEvent) {
|
||||
if (event) {
|
||||
@@ -69,6 +61,7 @@ export class ContextActionsDirective {
|
||||
) {}
|
||||
|
||||
private execute(event: MouseEvent) {
|
||||
// todo: review this in ADF
|
||||
const selected = this.getSelectedRow(event);
|
||||
|
||||
if (selected) {
|
||||
|
@@ -26,7 +26,37 @@
|
||||
import { ContextActionsDirective } from './context-menu.directive';
|
||||
|
||||
describe('ContextActionsDirective', () => {
|
||||
it('should be defined', () => {
|
||||
expect(ContextActionsDirective).toBeDefined();
|
||||
let directive;
|
||||
const contextMenuServiceMock = <any>{
|
||||
open: jasmine.createSpy('open')
|
||||
};
|
||||
const storeMock = <any>{};
|
||||
const documentListMock = <any>{};
|
||||
|
||||
beforeEach(() => {
|
||||
directive = new ContextActionsDirective(
|
||||
documentListMock,
|
||||
storeMock,
|
||||
contextMenuServiceMock
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render context menu when disable property is false', () => {
|
||||
directive.enabled = false;
|
||||
spyOn(directive, 'getSelectedRow').and.returnValue({});
|
||||
|
||||
directive.onContextMenuEvent(new MouseEvent('contextmenu'));
|
||||
|
||||
expect(contextMenuServiceMock.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render context menu when disable property is true', () => {
|
||||
directive.enabled = true;
|
||||
spyOn(directive, 'getSelectedRow').and.returnValue({});
|
||||
spyOn(directive, 'isInSelection').and.returnValue(true);
|
||||
|
||||
directive.onContextMenuEvent(new MouseEvent('contextmenu'));
|
||||
|
||||
expect(contextMenuServiceMock.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -35,9 +35,9 @@ import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
|
||||
|
||||
import { ContextActionsDirective } from './context-menu.directive';
|
||||
import { ContextMenuComponent } from './context-menu.component';
|
||||
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { OutsideEventDirective } from './context-menu-outside-event.directive';
|
||||
import { ContextMenuItemComponent } from './context-menu-item.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -52,13 +52,14 @@ import { OutsideEventDirective } from './context-menu-outside-event.directive';
|
||||
declarations: [
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent,
|
||||
ContextMenuItemDirective,
|
||||
ContextMenuItemComponent,
|
||||
OutsideEventDirective
|
||||
],
|
||||
exports: [
|
||||
OutsideEventDirective,
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent
|
||||
ContextMenuComponent,
|
||||
ContextMenuItemComponent
|
||||
],
|
||||
entryComponents: [ContextMenuComponent]
|
||||
})
|
||||
|
@@ -23,10 +23,48 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Overlay } from '@angular/cdk/overlay';
|
||||
import { Injector } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { ContextMenuService } from './context-menu.service';
|
||||
import { ContextMenuModule } from './context-menu.module';
|
||||
|
||||
describe('ContextMenuService', () => {
|
||||
it('should be defined', () => {
|
||||
expect(ContextMenuService).toBeDefined();
|
||||
let contextMenuService;
|
||||
const overlayConfig = {
|
||||
hasBackdrop: false,
|
||||
backdropClass: '',
|
||||
panelClass: 'test-panel'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ContextMenuModule],
|
||||
providers: [
|
||||
Overlay,
|
||||
{ provide: Store, useValue: { select: () => of() } },
|
||||
{ provide: AppConfigService, useValue: {} }
|
||||
]
|
||||
});
|
||||
|
||||
const injector = TestBed.get(Injector);
|
||||
const overlay = TestBed.get(Overlay);
|
||||
|
||||
contextMenuService = new ContextMenuService(injector, overlay);
|
||||
});
|
||||
|
||||
it('should create a custom overlay', () => {
|
||||
contextMenuService.open(overlayConfig);
|
||||
|
||||
expect(document.querySelector('.test-panel')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render component', () => {
|
||||
contextMenuService.open(overlayConfig);
|
||||
|
||||
expect(document.querySelector('aca-context-menu')).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
@@ -18,20 +18,6 @@ export class ContextMenuService {
|
||||
|
||||
this.attachDialogContainer(overlay, config, overlayRef);
|
||||
|
||||
overlay.backdropClick().subscribe(() => overlayRef.close());
|
||||
|
||||
// prevent native contextmenu on overlay element if config.hasBackdrop is true
|
||||
if (config.hasBackdrop) {
|
||||
(<any>overlay)._backdropElement.addEventListener(
|
||||
'contextmenu',
|
||||
() => {
|
||||
event.preventDefault();
|
||||
(<any>overlay)._backdropClick.next(null);
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return overlayRef;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,6 @@
|
||||
<div class="inner-layout__panel">
|
||||
<adf-document-list #documentList
|
||||
acaDocumentList
|
||||
acaContextActions
|
||||
[display]="documentDisplayMode$ | async"
|
||||
currentFolderId="-mysites-"
|
||||
selectionMode="single"
|
||||
|
Reference in New Issue
Block a user