diff --git a/docs/extending/application-actions.md b/docs/extending/application-actions.md index 9764c91f5..0d6108763 100644 --- a/docs/extending/application-actions.md +++ b/docs/extending/application-actions.md @@ -124,5 +124,6 @@ Below is the list of public actions types you can use in the plugin definitions | 1.8.0 | VIEW_NODE | NodeId<`string`> , [ViewNodeExtras](../features/file-viewer.md#details)<`any`> | Lightweight preview of a node by id. Can be invoked from extensions. For details also see [File Viewer](../features/file-viewer.md#details) | | 1.8.0 | CLOSE_PREVIEW | n/a | Closes the viewer ( preview of the item ) | | 1.9.0 | RESET_SELECTION | n/a | Resets active document list selection | -| 1.10.0 | FILE_FROM_TEMPLATE | n/a | Invoke dialogs flow for creating a file from selected template| -| 1.10.0 | CREATE_FILE_FROM_TEMPLATE | Node | Copy selected tetmplate into current folder | +| 1.10.0 | FILE_FROM_TEMPLATE | n/a | Invoke dialogs flow for creating a file from selected template| +| 1.10.0 | CREATE_FILE_FROM_TEMPLATE | Node | Copy selected template into current folder | +| 1.10.0 | CONTEXT_MENU | MouseEvent | Invoke context menu for [DocumentListComponent](https://www.alfresco.com/abn/adf/docs/content-services/components/document-list.component) | diff --git a/src/app/components/context-menu/context-menu.directives.spec.ts b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.spec.ts similarity index 77% rename from src/app/components/context-menu/context-menu.directives.spec.ts rename to projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.spec.ts index e32fe86fd..36f75c365 100644 --- a/src/app/components/context-menu/context-menu.directives.spec.ts +++ b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.spec.ts @@ -23,24 +23,25 @@ * along with Alfresco. If not, see . */ -import { ContextActionsDirective } from './context-menu.directive'; +import { ContextActionsDirective } from './contextmenu.directive'; +import { ContextMenu } from '@alfresco/aca-shared/store'; import { fakeAsync, tick } from '@angular/core/testing'; describe('ContextActionsDirective', () => { let directive; - const contextMenuServiceMock = { - open: jasmine.createSpy('open') + const storeMock = { + dispatch: jasmine.createSpy('dispatch') }; beforeEach(() => { - directive = new ContextActionsDirective(contextMenuServiceMock); + directive = new ContextActionsDirective(storeMock); }); it('should not render context menu when `enabled` property is false', () => { directive.enabled = false; directive.onContextMenuEvent(new MouseEvent('contextmenu')); - expect(contextMenuServiceMock.open).not.toHaveBeenCalled(); + expect(storeMock.dispatch).not.toHaveBeenCalled(); }); it('should call service to render context menu', fakeAsync(() => { @@ -51,13 +52,16 @@ describe('ContextActionsDirective', () => { const fragment = document.createDocumentFragment(); fragment.appendChild(el); const target = fragment.querySelector('div'); + const mouseEventMock = { preventDefault: () => {}, target }; directive.ngOnInit(); - directive.onContextMenuEvent({ preventDefault: () => {}, target }); + directive.onContextMenuEvent(mouseEventMock); tick(500); - expect(contextMenuServiceMock.open).toHaveBeenCalled(); + expect(storeMock.dispatch).toHaveBeenCalledWith( + new ContextMenu(mouseEventMock) + ); })); }); diff --git a/src/app/components/context-menu/context-menu.directive.ts b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.ts similarity index 69% rename from src/app/components/context-menu/context-menu.directive.ts rename to projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.ts index 140f3b5df..448b4b085 100644 --- a/src/app/components/context-menu/context-menu.directive.ts +++ b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.directive.ts @@ -30,18 +30,18 @@ import { OnInit, OnDestroy } from '@angular/core'; -import { ContextMenuOverlayRef } from './context-menu-overlay'; -import { ContextMenuService } from './context-menu.service'; -import { debounceTime } from 'rxjs/operators'; -import { Subject, fromEvent, Subscription } from 'rxjs'; +import { debounceTime, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { AppStore, ContextMenu } from '@alfresco/aca-shared/store'; @Directive({ - selector: '[acaContextActions]' + selector: '[acaContextActions]', + exportAs: 'acaContextActions' }) export class ContextActionsDirective implements OnInit, OnDestroy { private execute$: Subject = new Subject(); - private subscriptions: Subscription[] = []; - private overlayRef: ContextMenuOverlayRef = null; + onDestroy$: Subject = new Subject(); // tslint:disable-next-line:no-input-rename @Input('acaContextEnable') @@ -61,42 +61,30 @@ export class ContextActionsDirective implements OnInit, OnDestroy { } } - constructor(private contextMenuService: ContextMenuService) {} + constructor(private store: Store) {} ngOnInit() { - this.subscriptions.push( - fromEvent(document.body, 'contextmenu').subscribe(() => { - if (this.overlayRef) { - this.overlayRef.close(); - } - }), - - this.execute$.pipe(debounceTime(300)).subscribe((event: MouseEvent) => { - this.render(event); - }) - ); + this.execute$ + .pipe( + debounceTime(300), + takeUntil(this.onDestroy$) + ) + .subscribe((event: MouseEvent) => { + this.store.dispatch(new ContextMenu(event)); + }); } ngOnDestroy() { - this.subscriptions.forEach(subscription => subscription.unsubscribe()); - this.subscriptions = []; - this.execute$ = null; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } execute(event: MouseEvent, target: Element) { if (!this.isSelected(target)) { target.dispatchEvent(new MouseEvent('click')); } - this.execute$.next(event); - } - private render(event: MouseEvent) { - this.overlayRef = this.contextMenuService.open({ - source: event, - hasBackdrop: false, - backdropClass: 'cdk-overlay-transparent-backdrop', - panelClass: 'cdk-overlay-pane' - }); + this.execute$.next(event); } private getTarget(event: MouseEvent): Element { diff --git a/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.module.ts b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.module.ts new file mode 100644 index 000000000..30ecdc60d --- /dev/null +++ b/projects/aca-shared/src/lib/directives/contextmenu/contextmenu.module.ts @@ -0,0 +1,33 @@ +/*! + * @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 { ContextActionsDirective } from './contextmenu.directive'; + +@NgModule({ + declarations: [ContextActionsDirective], + exports: [ContextActionsDirective] +}) +export class ContextActionsModule {} diff --git a/projects/aca-shared/src/lib/shared.module.ts b/projects/aca-shared/src/lib/shared.module.ts index 18a1e5424..54b96cb65 100644 --- a/projects/aca-shared/src/lib/shared.module.ts +++ b/projects/aca-shared/src/lib/shared.module.ts @@ -27,8 +27,11 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { ContentApiService } from './services/content-api.service'; import { NodePermissionService } from './services/node-permission.service'; import { AppService } from './services/app.service'; - -@NgModule({}) +import { ContextActionsModule } from './directives/contextmenu/contextmenu.module'; +@NgModule({ + imports: [ContextActionsModule], + exports: [ContextActionsModule] +}) export class SharedModule { static forRoot(): ModuleWithProviders { return { diff --git a/projects/aca-shared/src/public-api.ts b/projects/aca-shared/src/public-api.ts index eeabe06e3..de7d4d9aa 100644 --- a/projects/aca-shared/src/public-api.ts +++ b/projects/aca-shared/src/public-api.ts @@ -39,4 +39,7 @@ export * from './lib/services/node-permission.service'; export * from './lib/components/generic-error/generic-error.component'; export * from './lib/components/generic-error/generic-error.module'; +export * from './lib/directives/contextmenu/contextmenu.directive'; +export * from './lib/directives/contextmenu/contextmenu.module'; + export * from './lib/shared.module'; diff --git a/projects/aca-shared/store/src/actions/contextmenu.actions.ts b/projects/aca-shared/store/src/actions/contextmenu.actions.ts new file mode 100644 index 000000000..c0cac2f7f --- /dev/null +++ b/projects/aca-shared/store/src/actions/contextmenu.actions.ts @@ -0,0 +1,36 @@ +/*! + * @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 { Action } from '@ngrx/store'; + +export enum ContextMenuActionTypes { + ContextMenu = 'CONTEXT_MENU' +} + +export class ContextMenu implements Action { + readonly type = ContextMenuActionTypes.ContextMenu; + + constructor(public event: MouseEvent) {} +} diff --git a/projects/aca-shared/store/src/public_api.ts b/projects/aca-shared/store/src/public_api.ts index 39bebd47b..3b6ce971a 100644 --- a/projects/aca-shared/store/src/public_api.ts +++ b/projects/aca-shared/store/src/public_api.ts @@ -33,6 +33,7 @@ export * from './actions/upload.actions'; export * from './actions/viewer.actions'; export * from './actions/metadata-aspect.actions'; export * from './actions/template.actions'; +export * from './actions/contextmenu.actions'; export * from './effects/dialog.effects'; export * from './effects/router.effects'; diff --git a/src/app/components/context-menu/context-menu.module.ts b/src/app/components/context-menu/context-menu.module.ts index 76c633769..07d7e2e71 100644 --- a/src/app/components/context-menu/context-menu.module.ts +++ b/src/app/components/context-menu/context-menu.module.ts @@ -35,7 +35,7 @@ import { AppCommonModule } from '../common/common.module'; import { ContextMenuItemComponent } from './context-menu-item.component'; import { OutsideEventDirective } from './context-menu-outside-event.directive'; import { ContextMenuComponent } from './context-menu.component'; -import { ContextActionsDirective } from './context-menu.directive'; +import { ContextActionsModule } from '@alfresco/aca-shared'; @NgModule({ imports: [ @@ -46,19 +46,19 @@ import { ContextActionsDirective } from './context-menu.directive'; CoreExtensionsModule.forChild(), CoreModule.forChild(), AppCommonModule, - ExtensionsModule + ExtensionsModule, + ContextActionsModule ], declarations: [ - ContextActionsDirective, ContextMenuComponent, ContextMenuItemComponent, OutsideEventDirective ], exports: [ OutsideEventDirective, - ContextActionsDirective, ContextMenuComponent, - ContextMenuItemComponent + ContextMenuItemComponent, + ContextActionsModule ], entryComponents: [ContextMenuComponent] }) diff --git a/src/app/store/app-store.module.ts b/src/app/store/app-store.module.ts index 1db3d4cb9..8286fe668 100644 --- a/src/app/store/app-store.module.ts +++ b/src/app/store/app-store.module.ts @@ -40,7 +40,8 @@ import { LibraryEffects, UploadEffects, FavoriteEffects, - TemplateEffects + TemplateEffects, + ContextMenuEffects } from './effects'; import { INITIAL_STATE } from './initial-state'; @@ -58,7 +59,8 @@ import { INITIAL_STATE } from './initial-state'; LibraryEffects, UploadEffects, FavoriteEffects, - TemplateEffects + TemplateEffects, + ContextMenuEffects ]), !environment.production ? StoreDevtoolsModule.instrument({ maxAge: 25 }) diff --git a/src/app/store/effects.ts b/src/app/store/effects.ts index 5856c9cd3..12f7862fd 100644 --- a/src/app/store/effects.ts +++ b/src/app/store/effects.ts @@ -33,3 +33,4 @@ export * from './effects/library.effects'; export * from './effects/upload.effects'; export * from './effects/upload.effects'; export * from './effects/template.effects'; +export * from './effects/contextmenu.effects'; diff --git a/src/app/store/effects/contextmenu.effects.spec.ts b/src/app/store/effects/contextmenu.effects.spec.ts new file mode 100644 index 000000000..0e992efad --- /dev/null +++ b/src/app/store/effects/contextmenu.effects.spec.ts @@ -0,0 +1,65 @@ +/*! + * @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 { TestBed } from '@angular/core/testing'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContextMenuEffects } from './contextmenu.effects'; +import { EffectsModule } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { ContextMenu } from '@alfresco/aca-shared/store'; +import { ContextMenuService } from '../../components/context-menu/context-menu.service'; + +describe('ContextMenuEffects', () => { + let store: Store; + let contextMenuService: ContextMenuService; + const overlayRefMock = { + close: jasmine.createSpy('close') + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AppTestingModule, EffectsModule.forRoot([ContextMenuEffects])], + providers: [ContextMenuService] + }); + + store = TestBed.get(Store); + contextMenuService = TestBed.get(ContextMenuService); + + spyOn(contextMenuService, 'open').and.returnValue(overlayRefMock); + }); + + it('should open dialog', () => { + store.dispatch(new ContextMenu(new MouseEvent('click'))); + expect(contextMenuService.open).toHaveBeenCalled(); + }); + + it('should close dialog reference if previously was opened', () => { + store.dispatch(new ContextMenu(new MouseEvent('click'))); + expect(contextMenuService.open).toHaveBeenCalled(); + + store.dispatch(new ContextMenu(new MouseEvent('click'))); + expect(overlayRefMock.close).toHaveBeenCalled(); + }); +}); diff --git a/src/app/store/effects/contextmenu.effects.ts b/src/app/store/effects/contextmenu.effects.ts new file mode 100644 index 000000000..0934262ba --- /dev/null +++ b/src/app/store/effects/contextmenu.effects.ts @@ -0,0 +1,61 @@ +/*! + * @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 { + ContextMenuActionTypes, + ContextMenu +} from '@alfresco/aca-shared/store'; +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { map } from 'rxjs/operators'; +import { ContextMenuOverlayRef } from '../../components/context-menu/context-menu-overlay'; +import { ContextMenuService } from '../../components/context-menu/context-menu.service'; + +@Injectable() +export class ContextMenuEffects { + private overlayRef: ContextMenuOverlayRef = null; + + constructor( + private contextMenuService: ContextMenuService, + private actions$: Actions + ) {} + + @Effect({ dispatch: false }) + contextMenu$ = this.actions$.pipe( + ofType(ContextMenuActionTypes.ContextMenu), + map(action => { + if (this.overlayRef) { + this.overlayRef.close(); + } + + this.overlayRef = this.contextMenuService.open({ + source: action.event, + hasBackdrop: false, + backdropClass: 'cdk-overlay-transparent-backdrop', + panelClass: 'cdk-overlay-pane' + }); + }) + ); +}