diff --git a/projects/aca-content/src/lib/components/common/logout/logout.component.ts b/projects/aca-content/src/lib/components/common/logout/logout.component.ts index 1f7026af4..e68b38daa 100644 --- a/projects/aca-content/src/lib/components/common/logout/logout.component.ts +++ b/projects/aca-content/src/lib/components/common/logout/logout.component.ts @@ -22,12 +22,12 @@ * from Hyland Software. If not, see . */ -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; import { SetSelectedNodesAction } from '@alfresco/aca-shared/store'; import { TranslateModule } from '@ngx-translate/core'; import { MatIconModule } from '@angular/material/icon'; -import { MatMenuModule } from '@angular/material/menu'; +import { MatMenuItem, MatMenuModule } from '@angular/material/menu'; import { LogoutDirective } from '@alfresco/adf-core'; @Component({ @@ -45,6 +45,9 @@ import { LogoutDirective } from '@alfresco/adf-core'; export class LogoutComponent { constructor(private store: Store) {} + @ViewChild(MatMenuItem) + menuItem: MatMenuItem; + onLogoutEvent() { this.store.dispatch(new SetSelectedNodesAction([])); } diff --git a/projects/aca-content/src/lib/components/common/user-info/user-info.component.html b/projects/aca-content/src/lib/components/common/user-info/user-info.component.html index 37bf7c773..74959936d 100644 --- a/projects/aca-content/src/lib/components/common/user-info/user-info.component.html +++ b/projects/aca-content/src/lib/components/common/user-info/user-info.component.html @@ -1,4 +1,4 @@ -
+
- + diff --git a/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts b/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts index cda6961ff..35d98ac7b 100644 --- a/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts +++ b/projects/aca-content/src/lib/components/common/user-info/user-info.component.ts @@ -22,10 +22,10 @@ * from Hyland Software. If not, see . */ -import { Component, ViewEncapsulation, inject } from '@angular/core'; +import { Component, ViewEncapsulation, inject, ViewChild } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; -import { MatMenuModule } from '@angular/material/menu'; +import { MatMenuItem, MatMenuModule } from '@angular/material/menu'; import { TranslateModule } from '@ngx-translate/core'; import { UserProfileService } from '@alfresco/aca-shared'; @@ -40,5 +40,8 @@ import { UserProfileService } from '@alfresco/aca-shared'; export class UserInfoComponent { private userProfileService = inject(UserProfileService); + @ViewChild(MatMenuItem) + menuItem: MatMenuItem; + user$ = this.userProfileService.userProfile$; } diff --git a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts new file mode 100644 index 000000000..bf8937a4c --- /dev/null +++ b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.spec.ts @@ -0,0 +1,61 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * 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 + * from Hyland Software. If not, see . + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatMenuTrigger } from '@angular/material/menu'; +import { CoreTestingModule, UnitTestingUtils } from '@alfresco/adf-core'; +import { UserMenuComponent } from './user-menu.component'; +import { AppExtensionService } from '@alfresco/aca-shared'; + +describe('UserMenuComponent', () => { + let component: UserMenuComponent; + let fixture: ComponentFixture; + let testingUtils: UnitTestingUtils; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CoreTestingModule, UserMenuComponent], + providers: [{ provide: AppExtensionService, useValue: { runActionById() {} } }] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserMenuComponent); + testingUtils = new UnitTestingUtils(fixture.debugElement); + + component = fixture.componentInstance; + component.data = { items: [{ id: 'id-1' }, { id: 'id-2' }] }; + fixture.detectChanges(); + }); + + it('should merge nested menu items to MatMenu in ngAfterViewInit', () => { + spyOn(component.menu, 'ngAfterContentInit').and.callThrough(); + testingUtils.getByDirective(MatMenuTrigger).nativeElement.click(); + fixture.detectChanges(); + component.ngAfterViewInit(); + + expect(component.menu._allItems.length).toBe(2); + expect(component.menu.ngAfterContentInit).toHaveBeenCalled(); + }); +}); diff --git a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts index e21c5db2a..441bec5c0 100644 --- a/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts +++ b/projects/aca-content/src/lib/components/sidenav/user-menu/user-menu.component.ts @@ -22,24 +22,24 @@ * from Hyland Software. If not, see . */ -import { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/core'; -import { ContentActionRef } from '@alfresco/adf-extensions'; +import { AfterViewInit, Component, inject, Input, OnInit, QueryList, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core'; +import { ContentActionRef, DynamicExtensionComponent } from '@alfresco/adf-extensions'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { TranslateModule } from '@ngx-translate/core'; -import { MatMenuModule } from '@angular/material/menu'; +import { MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu'; import { ToolbarMenuItemComponent, UserProfileService } from '@alfresco/aca-shared'; @Component({ standalone: true, - imports: [CommonModule, TranslateModule, MatButtonModule, MatMenuModule, ToolbarMenuItemComponent], + imports: [CommonModule, TranslateModule, MatButtonModule, MatMenuModule, ToolbarMenuItemComponent, DynamicExtensionComponent], selector: 'aca-user-menu', templateUrl: './user-menu.component.html', styleUrls: ['./user-menu.component.scss'], encapsulation: ViewEncapsulation.None, host: { class: 'aca-user-menu' } }) -export class UserMenuComponent implements OnInit { +export class UserMenuComponent implements OnInit, AfterViewInit { private userProfileService = inject(UserProfileService); user$ = this.userProfileService.userProfile$; @@ -50,9 +50,24 @@ export class UserMenuComponent implements OnInit { @Input() data: { items: any[] }; + @ViewChild(MatMenu) + menu: MatMenu; + + @ViewChildren(ToolbarMenuItemComponent) + toolbarMenuItems: QueryList; + ngOnInit() { if (this.data?.items) { this.data.items.sort((a, b) => a.order - b.order); } } + + ngAfterViewInit() { + const menuItems = this.toolbarMenuItems.map((toolbarMenuItem) => toolbarMenuItem.menuItem).filter((menuItem) => menuItem !== undefined); + + const menuItemsQueryList = new QueryList(); + menuItemsQueryList.reset(menuItems); + this.menu._allItems = menuItemsQueryList; + this.menu.ngAfterContentInit(); + } } diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts index 8a35d5a7e..402f9da14 100644 --- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts +++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.spec.ts @@ -27,11 +27,14 @@ import { ToolbarMenuItemComponent } from './toolbar-menu-item.component'; import { AppExtensionService } from '../../../services/app.extension.service'; import { Store } from '@ngrx/store'; import { of } from 'rxjs'; -import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions'; +import { ContentActionRef, ContentActionType, DynamicExtensionComponent } from '@alfresco/adf-extensions'; import { LibTestingModule } from '@alfresco/aca-shared'; +import { MatMenuItem } from '@angular/material/menu'; +import { UnitTestingUtils } from '@alfresco/adf-core'; describe('ToolbarMenuItemComponent', () => { let fixture: ComponentFixture; + let testingUtils: UnitTestingUtils; let component: ToolbarMenuItemComponent; let appExtensionService: AppExtensionService; @@ -51,6 +54,7 @@ describe('ToolbarMenuItemComponent', () => { }); fixture = TestBed.createComponent(ToolbarMenuItemComponent); + testingUtils = new UnitTestingUtils(fixture.debugElement); component = fixture.componentInstance; appExtensionService = TestBed.inject(AppExtensionService); }); @@ -110,4 +114,17 @@ describe('ToolbarMenuItemComponent', () => { expect(component.trackByActionId(0, contentActionRef)).toBe('action1'); }); + + it('should assign menuItem from dynamic component', () => { + component.actionRef = { + id: 'action1', + type: ContentActionType.custom + }; + fixture.detectChanges(); + + const innerElement = testingUtils.getByDirective(DynamicExtensionComponent); + innerElement.componentInstance.menuItem = new MatMenuItem(null, null, null, null, null); + component.ngAfterViewInit(); + expect(component.menuItem).toBeInstanceOf(MatMenuItem); + }); }); diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts index 69053e263..d6193c455 100644 --- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts +++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu-item/toolbar-menu-item.component.ts @@ -22,7 +22,7 @@ * from Hyland Software. If not, see . */ -import { Component, Input, ViewChild, ViewEncapsulation } from '@angular/core'; +import { AfterViewInit, Component, Input, ViewChild, ViewEncapsulation } from '@angular/core'; import { ContentActionRef, DynamicExtensionComponent } from '@alfresco/adf-extensions'; import { AppExtensionService } from '../../../services/app.extension.service'; import { MatMenuItem, MatMenuModule } from '@angular/material/menu'; @@ -40,7 +40,7 @@ import { IconComponent } from '@alfresco/adf-core'; encapsulation: ViewEncapsulation.None, host: { class: 'app-toolbar-menu-item' } }) -export class ToolbarMenuItemComponent { +export class ToolbarMenuItemComponent implements AfterViewInit { @Input() actionRef: ContentActionRef; @Input() @@ -49,6 +49,9 @@ export class ToolbarMenuItemComponent { @ViewChild(MatMenuItem) menuItem: MatMenuItem; + @ViewChild(DynamicExtensionComponent) + dynamicComponent: DynamicExtensionComponent; + constructor(private extensions: AppExtensionService) {} runAction() { @@ -64,6 +67,12 @@ export class ToolbarMenuItemComponent { } } + ngAfterViewInit() { + if (this.dynamicComponent?.menuItem) { + this.menuItem = this.dynamicComponent.menuItem; + } + } + private hasClickAction(actionRef: ContentActionRef): boolean { return !!actionRef?.actions?.click; }