mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACA-1614] DocumentList - context menu actions (#544)
* context menu * make same structure check * align naming * lazy loading support * update module import implementation * close context menu on Escape * focus and navigate context menu items * update with material cdk 'keycodes' name * changed module folder name
This commit is contained in:
@@ -39,7 +39,8 @@
|
|||||||
"unindent",
|
"unindent",
|
||||||
"exif",
|
"exif",
|
||||||
"cardview",
|
"cardview",
|
||||||
"webm"
|
"webm",
|
||||||
|
"keycodes"
|
||||||
],
|
],
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
"html",
|
"html",
|
||||||
|
@@ -76,6 +76,7 @@ import { DirectivesModule } from './directives/directives.module';
|
|||||||
import { ToggleInfoDrawerComponent } from './components/toolbar/toggle-info-drawer/toggle-info-drawer.component';
|
import { ToggleInfoDrawerComponent } from './components/toolbar/toggle-info-drawer/toggle-info-drawer.component';
|
||||||
import { DocumentDisplayModeComponent } from './components/toolbar/document-display-mode/document-display-mode.component';
|
import { DocumentDisplayModeComponent } from './components/toolbar/document-display-mode/document-display-mode.component';
|
||||||
import { ToggleFavoriteComponent } from './components/toolbar/toggle-favorite/toggle-favorite.component';
|
import { ToggleFavoriteComponent } from './components/toolbar/toggle-favorite/toggle-favorite.component';
|
||||||
|
import { ContextMenuModule } from './context-menu/context-menu.module';
|
||||||
|
|
||||||
export function setupExtensionServiceFactory(service: ExtensionService): Function {
|
export function setupExtensionServiceFactory(service: ExtensionService): Function {
|
||||||
return () => service.load();
|
return () => service.load();
|
||||||
@@ -98,6 +99,7 @@ export function setupExtensionServiceFactory(service: ExtensionService): Functio
|
|||||||
ExtensionsModule,
|
ExtensionsModule,
|
||||||
|
|
||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
|
ContextMenuModule.forRoot(),
|
||||||
AppInfoDrawerModule
|
AppInfoDrawerModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -13,7 +13,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-favorites-"
|
currentFolderId="-favorites-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
|
@@ -24,7 +24,10 @@
|
|||||||
[parentId]="node?.id"
|
[parentId]="node?.id"
|
||||||
[disabled]="!canUpload">
|
[disabled]="!canUpload">
|
||||||
|
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
[sorting]="[ 'modifiedAt', 'desc' ]"
|
[sorting]="[ 'modifiedAt', 'desc' ]"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
|
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-mysites-"
|
currentFolderId="-mysites-"
|
||||||
selectionMode="single"
|
selectionMode="single"
|
||||||
|
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-recent-"
|
currentFolderId="-recent-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
|
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-sharedlinks-"
|
currentFolderId="-sharedlinks-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
|
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list #documentList
|
||||||
|
acaDocumentList
|
||||||
|
acaContextActions
|
||||||
|
[acaContextEnable]="selection.count"
|
||||||
[display]="documentDisplayMode$ | async"
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-trashcan-"
|
currentFolderId="-trashcan-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
|
52
src/app/context-menu/animations.ts
Normal file
52
src/app/context-menu/animations.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*!
|
||||||
|
* @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 })))
|
||||||
|
];
|
51
src/app/context-menu/context-menu-item.directive.ts
Normal file
51
src/app/context-menu/context-menu-item.directive.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Directive, ElementRef, OnDestroy } from '@angular/core';
|
||||||
|
import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[acaContextMenuItem]',
|
||||||
|
})
|
||||||
|
export class ContextMenuItemDirective implements OnDestroy, FocusableOption {
|
||||||
|
constructor(
|
||||||
|
private elementRef: ElementRef,
|
||||||
|
private focusMonitor: FocusMonitor) {
|
||||||
|
|
||||||
|
focusMonitor.monitor(this.getHostElement(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.focusMonitor.stopMonitoring(this.getHostElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
focus(origin: FocusOrigin = 'keyboard'): void {
|
||||||
|
this.focusMonitor.focusVia(this.getHostElement(), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHostElement(): HTMLElement {
|
||||||
|
return this.elementRef.nativeElement;
|
||||||
|
}
|
||||||
|
}
|
35
src/app/context-menu/context-menu-overlay.ts
Normal file
35
src/app/context-menu/context-menu-overlay.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
|
||||||
|
export class ContextMenuOverlayRef {
|
||||||
|
|
||||||
|
constructor(private overlayRef: OverlayRef) { }
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.overlayRef.dispose();
|
||||||
|
}
|
||||||
|
}
|
12
src/app/context-menu/context-menu.component.html
Normal file
12
src/app/context-menu/context-menu.component.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<div mat-menu class="mat-menu-panel" @panelAnimation>
|
||||||
|
<div class="mat-menu-content">
|
||||||
|
<ng-container *ngFor="let entry of actions">
|
||||||
|
<button mat-menu-item
|
||||||
|
acaContextMenuItem
|
||||||
|
(click)="runAction(entry.actions.click)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
<span>{{ entry.title | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
117
src/app/context-menu/context-menu.component.ts
Normal file
117
src/app/context-menu/context-menu.component.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*!
|
||||||
|
* @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 {
|
||||||
|
Component, ViewEncapsulation, OnInit, OnDestroy, HostListener,
|
||||||
|
ViewChildren, QueryList, 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 { ExtensionService } from '../extensions/extension.service';
|
||||||
|
import { AppStore, SelectionState } from '../store/states';
|
||||||
|
import { appSelection } from '../store/selectors/app.selectors';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Subject } from 'rxjs/Rx';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||||
|
import { ContentActionRef } from '../extensions/action.extensions';
|
||||||
|
import { contextMenuAnimation } from './animations';
|
||||||
|
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'aca-context-menu',
|
||||||
|
templateUrl: './context-menu.component.html',
|
||||||
|
host: { 'role': 'menu' },
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
animations: [
|
||||||
|
trigger('panelAnimation', contextMenuAnimation)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
|
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
private selection: SelectionState;
|
||||||
|
private _keyManager: FocusKeyManager<ContextMenuItemDirective>;
|
||||||
|
actions: Array<ContentActionRef> = [];
|
||||||
|
|
||||||
|
@ViewChildren(ContextMenuItemDirective)
|
||||||
|
private contextMenuItems: QueryList<ContextMenuItemDirective>;
|
||||||
|
|
||||||
|
@HostListener('document:keydown.Escape', ['$event'])
|
||||||
|
handleKeydownEscape(event: KeyboardEvent) {
|
||||||
|
if (event) {
|
||||||
|
this.contextMenuOverlayRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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: ExtensionService,
|
||||||
|
private store: Store<AppStore>,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
runAction(actionId: string) {
|
||||||
|
const context = {
|
||||||
|
selection: this.selection
|
||||||
|
};
|
||||||
|
|
||||||
|
this.extensions.runActionById(actionId, context);
|
||||||
|
this.contextMenuOverlayRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy$.next(true);
|
||||||
|
this.onDestroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection.count) {
|
||||||
|
this.selection = selection;
|
||||||
|
this.actions = this.extensions.getAllowedContentContextActions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(this.contextMenuItems);
|
||||||
|
this._keyManager.setFirstItemActive();
|
||||||
|
}
|
||||||
|
}
|
66
src/app/context-menu/context-menu.directive.ts
Normal file
66
src/app/context-menu/context-menu.directive.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Directive, HostListener, Input } from '@angular/core';
|
||||||
|
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||||
|
import { ContextMenuService } from './context-menu.service';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[acaContextActions]'
|
||||||
|
})
|
||||||
|
export class ContextActionsDirective {
|
||||||
|
private overlayRef: ContextMenuOverlayRef = null;
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-input-rename
|
||||||
|
@Input('acaContextEnable') enabled: boolean;
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
onResize(event) {
|
||||||
|
if (event && this.overlayRef) {
|
||||||
|
this.overlayRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('contextmenu', ['$event'])
|
||||||
|
onContextmenu(event: MouseEvent) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (this.enabled) {
|
||||||
|
this.render(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private contextMenuService: ContextMenuService) { }
|
||||||
|
|
||||||
|
private render(event: MouseEvent) {
|
||||||
|
this.overlayRef = this.contextMenuService.open({
|
||||||
|
source: event,
|
||||||
|
hasBackdrop: true,
|
||||||
|
panelClass: 'cdk-overlay-pane',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
73
src/app/context-menu/context-menu.module.ts
Normal file
73
src/app/context-menu/context-menu.module.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { NgModule, ModuleWithProviders } from '@angular/core';
|
||||||
|
import { MatMenuModule, MatListModule, MatIconModule, MatButtonModule } from '@angular/material';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { ContextActionsDirective } from './context-menu.directive';
|
||||||
|
import { ContextMenuService } from './context-menu.service';
|
||||||
|
import { ContextMenuComponent } from './context-menu.component';
|
||||||
|
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||||
|
import { CoreModule } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
MatMenuModule,
|
||||||
|
MatListModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatButtonModule,
|
||||||
|
BrowserModule,
|
||||||
|
CoreModule.forChild()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ContextActionsDirective,
|
||||||
|
ContextMenuComponent,
|
||||||
|
ContextMenuItemDirective
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ContextActionsDirective,
|
||||||
|
ContextMenuComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
ContextMenuComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ContextMenuModule {
|
||||||
|
static forRoot(): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: ContextMenuModule,
|
||||||
|
providers: [
|
||||||
|
ContextMenuService
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forChild(): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: ContextMenuModule
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
100
src/app/context-menu/context-menu.service.ts
Normal file
100
src/app/context-menu/context-menu.service.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Injectable, Injector, ComponentRef, ElementRef } from '@angular/core';
|
||||||
|
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
|
||||||
|
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||||
|
import { ContextMenuComponent } from './context-menu.component';
|
||||||
|
import { ContextmenuOverlayConfig } from './interfaces';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ContextMenuService {
|
||||||
|
constructor(
|
||||||
|
private injector: Injector,
|
||||||
|
private overlay: Overlay) { }
|
||||||
|
|
||||||
|
open(config: ContextmenuOverlayConfig) {
|
||||||
|
|
||||||
|
const overlay = this.createOverlay(config);
|
||||||
|
|
||||||
|
const overlayRef = new ContextMenuOverlayRef(overlay);
|
||||||
|
|
||||||
|
this.attachDialogContainer(overlay, config, overlayRef);
|
||||||
|
|
||||||
|
overlay.backdropClick().subscribe(() => overlayRef.close());
|
||||||
|
|
||||||
|
// prevent native contextmenu on overlay element if config.hasBackdrop is true
|
||||||
|
(<any>overlay)._backdropElement
|
||||||
|
.addEventListener('contextmenu', () => {
|
||||||
|
event.preventDefault();
|
||||||
|
(<any>overlay)._backdropClick.next(null);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
return overlayRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOverlay(config: ContextmenuOverlayConfig) {
|
||||||
|
const overlayConfig = this.getOverlayConfig(config);
|
||||||
|
return this.overlay.create(overlayConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private attachDialogContainer(overlay: OverlayRef, config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef) {
|
||||||
|
const injector = this.createInjector(config, contextmenuOverlayRef);
|
||||||
|
|
||||||
|
const containerPortal = new ComponentPortal(ContextMenuComponent, null, injector);
|
||||||
|
const containerRef: ComponentRef<ContextMenuComponent> = overlay.attach(containerPortal);
|
||||||
|
|
||||||
|
return containerRef.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInjector(config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef): PortalInjector {
|
||||||
|
const injectionTokens = new WeakMap();
|
||||||
|
|
||||||
|
injectionTokens.set(ContextMenuOverlayRef, contextmenuOverlayRef);
|
||||||
|
|
||||||
|
return new PortalInjector(this.injector, injectionTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOverlayConfig(config: ContextmenuOverlayConfig): OverlayConfig {
|
||||||
|
const fakeElement: any = {
|
||||||
|
getBoundingClientRect: (): ClientRect => ({
|
||||||
|
bottom: config.source.clientY,
|
||||||
|
height: 0,
|
||||||
|
left: config.source.clientX,
|
||||||
|
right: config.source.clientX,
|
||||||
|
top: config.source.clientY,
|
||||||
|
width: 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const positionStrategy = this.overlay.position()
|
||||||
|
.connectedTo(
|
||||||
|
new ElementRef(fakeElement),
|
||||||
|
{ originX: 'start', originY: 'bottom' },
|
||||||
|
{ overlayX: 'start', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'top' },
|
||||||
|
{ overlayX: 'start', overlayY: 'bottom' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'end', originY: 'top' },
|
||||||
|
{ overlayX: 'start', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'top' },
|
||||||
|
{ overlayX: 'end', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'end', originY: 'center' },
|
||||||
|
{ overlayX: 'start', overlayY: 'center' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'center' },
|
||||||
|
{ overlayX: 'end', overlayY: 'center' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayConfig = new OverlayConfig({
|
||||||
|
hasBackdrop: config.hasBackdrop,
|
||||||
|
backdropClass: config.backdropClass,
|
||||||
|
panelClass: config.panelClass,
|
||||||
|
scrollStrategy: this.overlay.scrollStrategies.close(),
|
||||||
|
positionStrategy
|
||||||
|
});
|
||||||
|
|
||||||
|
return overlayConfig;
|
||||||
|
}
|
||||||
|
}
|
32
src/app/context-menu/interfaces.ts
Normal file
32
src/app/context-menu/interfaces.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*!
|
||||||
|
* @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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ContextmenuOverlayConfig {
|
||||||
|
panelClass?: string;
|
||||||
|
hasBackdrop?: boolean;
|
||||||
|
backdropClass?: string;
|
||||||
|
source?: MouseEvent;
|
||||||
|
data?: any;
|
||||||
|
}
|
@@ -46,6 +46,7 @@ export interface ExtensionConfig {
|
|||||||
navbar?: Array<NavBarGroupRef>;
|
navbar?: Array<NavBarGroupRef>;
|
||||||
content?: {
|
content?: {
|
||||||
actions?: Array<ContentActionRef>;
|
actions?: Array<ContentActionRef>;
|
||||||
|
contextActions?: Array<ContentActionRef>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -54,6 +54,7 @@ export class ExtensionService implements RuleContext {
|
|||||||
|
|
||||||
contentActions: Array<ContentActionRef> = [];
|
contentActions: Array<ContentActionRef> = [];
|
||||||
viewerActions: Array<ContentActionRef> = [];
|
viewerActions: Array<ContentActionRef> = [];
|
||||||
|
contentContextmenuActions: Array<ContentActionRef> = [];
|
||||||
openWithActions: Array<ContentActionRef> = [];
|
openWithActions: Array<ContentActionRef> = [];
|
||||||
createActions: Array<ContentActionRef> = [];
|
createActions: Array<ContentActionRef> = [];
|
||||||
navbar: Array<NavBarGroupRef> = [];
|
navbar: Array<NavBarGroupRef> = [];
|
||||||
@@ -124,6 +125,7 @@ export class ExtensionService implements RuleContext {
|
|||||||
this.routes = this.loadRoutes(config);
|
this.routes = this.loadRoutes(config);
|
||||||
this.contentActions = this.loadContentActions(config);
|
this.contentActions = this.loadContentActions(config);
|
||||||
this.viewerActions = this.loadViewerActions(config);
|
this.viewerActions = this.loadViewerActions(config);
|
||||||
|
this.contentContextmenuActions = this.loadContentContextmenuActions(config);
|
||||||
this.openWithActions = this.loadViewerOpenWith(config);
|
this.openWithActions = this.loadViewerOpenWith(config);
|
||||||
this.createActions = this.loadCreateActions(config);
|
this.createActions = this.loadCreateActions(config);
|
||||||
this.navbar = this.loadNavBar(config);
|
this.navbar = this.loadNavBar(config);
|
||||||
@@ -173,6 +175,14 @@ export class ExtensionService implements RuleContext {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected loadContentContextmenuActions(config: ExtensionConfig): Array<ContentActionRef> {
|
||||||
|
if (config && config.features && config.features.content) {
|
||||||
|
return (config.features.content.contextActions || [])
|
||||||
|
.sort(this.sortByOrder);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
protected loadNavBar(config: ExtensionConfig): any {
|
protected loadNavBar(config: ExtensionConfig): any {
|
||||||
if (config && config.features) {
|
if (config && config.features) {
|
||||||
return (config.features.navbar || [])
|
return (config.features.navbar || [])
|
||||||
@@ -335,6 +345,12 @@ export class ExtensionService implements RuleContext {
|
|||||||
.filter(action => this.filterByRules(action));
|
.filter(action => this.filterByRules(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllowedContentContextActions(): Array<ContentActionRef> {
|
||||||
|
return this.contentContextmenuActions
|
||||||
|
.filter(this.filterEnabled)
|
||||||
|
.filter(action => this.filterByRules(action));
|
||||||
|
}
|
||||||
|
|
||||||
reduceSeparators(
|
reduceSeparators(
|
||||||
acc: ContentActionRef[],
|
acc: ContentActionRef[],
|
||||||
el: ContentActionRef,
|
el: ContentActionRef,
|
||||||
|
@@ -452,6 +452,175 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"contextActions": [
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.download",
|
||||||
|
"type": "button",
|
||||||
|
"order": 10,
|
||||||
|
"title": "APP.ACTIONS.DOWNLOAD",
|
||||||
|
"icon": "get_app",
|
||||||
|
"actions": {
|
||||||
|
"click": "DOWNLOAD_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canDownload"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.preview",
|
||||||
|
"type": "button",
|
||||||
|
"order": 15,
|
||||||
|
"title": "APP.ACTIONS.VIEW",
|
||||||
|
"icon": "open_in_browser",
|
||||||
|
"actions": {
|
||||||
|
"click": "VIEW_FILE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canViewFile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.editFolder",
|
||||||
|
"type": "button",
|
||||||
|
"order": 20,
|
||||||
|
"title": "APP.ACTIONS.EDIT",
|
||||||
|
"icon": "create",
|
||||||
|
"actions": {
|
||||||
|
"click": "EDIT_FOLDER"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canEditFolder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.share",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.SHARE",
|
||||||
|
"order": 25,
|
||||||
|
"icon": "share",
|
||||||
|
"actions": {
|
||||||
|
"click": "SHARE_NODE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.file.canShare"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.favorite.add",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"order": 30,
|
||||||
|
"icon": "star_border",
|
||||||
|
"actions": {
|
||||||
|
"click": "ADD_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canAdd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.favorite.remove",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"order": 30,
|
||||||
|
"icon": "star",
|
||||||
|
"actions": {
|
||||||
|
"click": "REMOVE_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canRemove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.copy",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.COPY",
|
||||||
|
"order": 35,
|
||||||
|
"icon": "content_copy",
|
||||||
|
"actions": {
|
||||||
|
"click": "COPY_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canCopyNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.move",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.MOVE",
|
||||||
|
"order": 40,
|
||||||
|
"icon": "library_books",
|
||||||
|
"actions": {
|
||||||
|
"click": "MOVE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.delete",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.DELETE",
|
||||||
|
"order": 45,
|
||||||
|
"icon": "delete",
|
||||||
|
"actions": {
|
||||||
|
"click": "DELETE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.versions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.VERSIONS",
|
||||||
|
"order": 50,
|
||||||
|
"icon": "history",
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_VERSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.versions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.permissions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.PERMISSIONS",
|
||||||
|
"icon": "settings_input_component",
|
||||||
|
"order": 55,
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_PERMISSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.permissions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.purgeDeletedNodes",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.DELETE_PERMANENT",
|
||||||
|
"icon": "delete_forever",
|
||||||
|
"actions": {
|
||||||
|
"click": "PURGE_DELETED_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.trashcan.hasSelection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.contextmenu.restoreDeletedNodes",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.RESTORE",
|
||||||
|
"icon": "restore",
|
||||||
|
"actions": {
|
||||||
|
"click": "RESTORE_DELETED_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.trashcan.hasSelection"
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
|
Reference in New Issue
Block a user