mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACS-3742] Layout changes for workspace (#2980)
* Layout changes for workspace sidemenu * added header and search layout changes * implemented review comments and removed process related code * Added expand and collapse functionality * Modified the paths * linting fixes * use standard material settings icon * use only specific modules needed for page layout * use standard "menu" icon for now * use standard avatar icon for now * cleanup user profile menu item * cleanup About component layout * remove hardcoded settings route * deprecate "headerImagePath" * deprecate "headerTextColor" customisation * deprecate "headerColor" customisation * proper toggle of the side menu * proper sidebar header implementation * user profile basic cleanup * minor fixes * cleanup buttons * remove old app layout and use ADF one * remove old header component * cleanup old layout module * fix unit tests * cleanup unit tests * cleanup header actions module * deprecate unused main-action component * cleanup styles * restore removed method * cleanup search results toolbar * restore expand menu functionality * cleanup code, back buttons for about and profile * restore original code * proper collapse button * remove unused i18n key * styles cleanup * cleanup sidebar * cleanup user profile * add safety checks for focus after close * layout fixes * update view profile unit tests * code cleanup after reviews * cleanup header actions * fix menu population, user info * improved upload and create actions * remove useless tests * fix folder rules tests * search button workaround * e2e: remove wait * add create/upload tooltips * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * e2e fix * try fix e2e * update e2e extension configs * try fix e2e * try fix e2e * fix eslint config * try fix e2e * move search button to extensions * move upload and create to extensions * remove header actions as no longer needed * cleanup * e2e fixes and cleanup for unwanted files * linting fixes * linting fixes * added button type to support text buttons * linting fixes * added more unit tests to achieve code coverage requirement * fixing code covergae for aca-content * fixed code coverage for aca-shared * linting fixes * linting fixes * cleanup * version fix --------- Co-authored-by: SheenaMalhotra182 <sheena.malhotra@globallogic.com> Co-authored-by: Denys Vuika <denys.vuika@gmail.com> Co-authored-by: SheenaMalhotra182 <sheena.malhotra@contractors.onbase.com>
This commit is contained in:
@@ -42,7 +42,7 @@ import {
|
||||
LibraryStatusColumnComponent,
|
||||
TrashcanNameColumnComponent
|
||||
} from '@alfresco/adf-content-services';
|
||||
import { DocumentBasePageService, ExtensionsDataLoaderGuard, RouterExtensionService, SharedModule } from '@alfresco/aca-shared';
|
||||
import { DocumentBasePageService, ExtensionsDataLoaderGuard, PageLayoutModule, RouterExtensionService, SharedModule } from '@alfresco/aca-shared';
|
||||
import * as rules from '@alfresco/aca-shared/rules';
|
||||
|
||||
import { FilesComponent } from './components/files/files.component';
|
||||
@@ -61,11 +61,9 @@ import { AppToolbarModule } from './components/toolbar/toolbar.module';
|
||||
import { AppCreateMenuModule } from './components/create-menu/create-menu.module';
|
||||
import { AppSidenavModule } from './components/sidenav/sidenav.module';
|
||||
import { AppCommonModule } from './components/common/common.module';
|
||||
import { AppLayoutModule } from './components/layout/layout.module';
|
||||
import { AppSearchInputModule } from './components/search/search-input.module';
|
||||
import { DocumentListCustomComponentsModule } from './components/dl-custom-components/document-list-custom-components.module';
|
||||
import { AppSearchResultsModule } from './components/search/search-results.module';
|
||||
import { AppHeaderModule } from './components/header/header.module';
|
||||
import { AppNodeVersionModule } from './components/node-version/node-version.module';
|
||||
import { FavoritesComponent } from './components/favorites/favorites.component';
|
||||
import { RecentFilesComponent } from './components/recent-files/recent-files.component';
|
||||
@@ -96,7 +94,6 @@ import { LocationLinkComponent } from './components/common/location-link/locatio
|
||||
import { LogoutComponent } from './components/common/logout/logout.component';
|
||||
import { ToggleSharedComponent } from './components/common/toggle-shared/toggle-shared.component';
|
||||
import { CustomNameColumnComponent } from './components/dl-custom-components/name-column/name-column.component';
|
||||
import { AppHeaderComponent } from './components/header/header.component';
|
||||
import { CommentsTabComponent } from './components/info-drawer/comments-tab/comments-tab.component';
|
||||
import { LibraryMetadataTabComponent } from './components/info-drawer/library-metadata-tab/library-metadata-tab.component';
|
||||
import { MetadataTabComponent } from './components/info-drawer/metadata-tab/metadata-tab.component';
|
||||
@@ -113,14 +110,14 @@ import { ViewNodeComponent } from './components/toolbar/view-node/view-node.comp
|
||||
import { CONTENT_ROUTES } from './aca-content.routes';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UploadFilesDialogComponent } from './components/upload-files-dialog/upload-files-dialog.component';
|
||||
import { SidenavWrapperComponent } from './components/sidenav/sidenav-wrapper/sidenav-wrapper.component';
|
||||
import { AppLayoutComponent } from './components/layout/app-layout/app-layout.component';
|
||||
import { AppTrashcanModule } from './components/trashcan/trashcan.module';
|
||||
import { AppSharedLinkViewModule } from './components/shared-link-view/shared-link-view.module';
|
||||
import { AcaFolderRulesModule } from '@alfresco/aca-folder-rules';
|
||||
import { TagsColumnComponent } from './components/dl-custom-components/tags-column/tags-column.component';
|
||||
import { ContentManagementService } from './services/content-management.service';
|
||||
import { UserInfoComponent } from './components/common/user-info/user-info.component';
|
||||
import { SidenavComponent } from './components/sidenav/sidenav.component';
|
||||
import { ContentManagementService } from './services/content-management.service';
|
||||
import { ShellLayoutComponent, SHELL_NAVBAR_MIN_WIDTH } from '@alfresco/adf-core/shell';
|
||||
|
||||
registerLocaleData(localeFr);
|
||||
registerLocaleData(localeDe);
|
||||
@@ -153,7 +150,7 @@ registerLocaleData(localeSv);
|
||||
MaterialModule,
|
||||
AppStoreModule,
|
||||
AppCommonModule,
|
||||
AppLayoutModule,
|
||||
PageLayoutModule,
|
||||
DirectivesModule,
|
||||
ContextMenuModule,
|
||||
AppInfoDrawerModule,
|
||||
@@ -163,7 +160,6 @@ registerLocaleData(localeSv);
|
||||
DocumentListCustomComponentsModule,
|
||||
AppSearchInputModule,
|
||||
AppSearchResultsModule,
|
||||
AppHeaderModule,
|
||||
AppNodeVersionModule,
|
||||
HammerModule,
|
||||
ViewProfileModule,
|
||||
@@ -194,7 +190,8 @@ registerLocaleData(localeSv);
|
||||
name: 'app',
|
||||
source: 'assets'
|
||||
}
|
||||
}
|
||||
},
|
||||
{ provide: SHELL_NAVBAR_MIN_WIDTH, useValue: 0 }
|
||||
]
|
||||
})
|
||||
export class ContentServiceExtensionModule {
|
||||
@@ -205,9 +202,8 @@ export class ContentServiceExtensionModule {
|
||||
});
|
||||
|
||||
extensions.setComponents({
|
||||
'app.layout.main': AppLayoutComponent,
|
||||
'app.layout.header': AppHeaderComponent,
|
||||
'app.layout.sidenav': SidenavWrapperComponent,
|
||||
'app.layout.main': ShellLayoutComponent,
|
||||
'app.layout.sidenav': SidenavComponent,
|
||||
'app.shell.sibling': UploadFilesDialogComponent,
|
||||
'app.components.tabs.metadata': MetadataTabComponent,
|
||||
'app.components.tabs.library.metadata': LibraryMetadataTabComponent,
|
||||
@@ -300,7 +296,10 @@ export class ContentServiceExtensionModule {
|
||||
'repository.isQuickShareEnabled': rules.hasQuickShareEnabled,
|
||||
'user.isAdmin': rules.isAdmin,
|
||||
'app.canShowLogout': rules.canShowLogout,
|
||||
'app.isContentServiceEnabled': rules.isContentServiceEnabled
|
||||
'app.isContentServiceEnabled': rules.isContentServiceEnabled,
|
||||
'app.isUploadSupported': rules.isUploadSupported,
|
||||
'app.canCreateLibrary': rules.canCreateLibrary,
|
||||
'app.isSearchSupported': rules.isSearchSupported
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,6 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppLayoutComponent } from './components/layout/app-layout/app-layout.component';
|
||||
import { FilesComponent } from './components/files/files.component';
|
||||
import { LibrariesComponent } from './components/libraries/libraries.component';
|
||||
import { FavoriteLibrariesComponent } from './components/favorite-libraries/favorite-libraries.component';
|
||||
@@ -40,6 +39,7 @@ import { ViewProfileRuleGuard } from './components/view-profile/view-profile.gua
|
||||
import { Route } from '@angular/router';
|
||||
import { SharedLinkViewComponent } from './components/shared-link-view/shared-link-view.component';
|
||||
import { TrashcanComponent } from './components/trashcan/trashcan.component';
|
||||
import { ShellLayoutComponent } from '@alfresco/adf-core/shell';
|
||||
|
||||
export const CONTENT_ROUTES: ExtensionRoute[] = [
|
||||
{
|
||||
@@ -56,7 +56,7 @@ export const CONTENT_ROUTES: ExtensionRoute[] = [
|
||||
},
|
||||
{
|
||||
path: 'view',
|
||||
component: AppLayoutComponent,
|
||||
component: ShellLayoutComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':nodeId',
|
||||
|
@@ -33,9 +33,10 @@ import { LanguagePickerComponent } from './language-picker/language-picker.compo
|
||||
import { LogoutComponent } from './logout/logout.component';
|
||||
import { ContentModule } from '@alfresco/adf-content-services';
|
||||
import { UserInfoComponent } from './user-info/user-info.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CoreModule.forChild(), ContentModule.forChild(), ExtensionsModule, GenericErrorModule],
|
||||
imports: [CommonModule, CoreModule.forChild(), ContentModule.forChild(), ExtensionsModule, GenericErrorModule, RouterModule],
|
||||
declarations: [LocationLinkComponent, ToggleSharedComponent, LanguagePickerComponent, LogoutComponent, UserInfoComponent],
|
||||
exports: [
|
||||
ExtensionsModule,
|
||||
|
@@ -1,14 +1,4 @@
|
||||
<ng-container>
|
||||
<adf-content-user-info
|
||||
*ngIf="mode === userInfoMode.CONTENT || mode === userInfoMode.CONTENT_SSO"
|
||||
[ecmUser]="ecmUser$ | async"
|
||||
[identityUser]="identityUser$ | async"
|
||||
[isLoggedIn]="isLoggedIn"
|
||||
[mode]="mode"
|
||||
></adf-content-user-info>
|
||||
<adf-identity-user-info
|
||||
*ngIf="mode === userInfoMode.SSO"
|
||||
[identityUser]="identityUser$ | async"
|
||||
[isLoggedIn]="isLoggedIn"
|
||||
></adf-identity-user-info>
|
||||
</ng-container>
|
||||
<button mat-menu-item [routerLink]="['/profile']" title="{{'APP.TOOLTIPS.MY_PROFILE' | translate}}">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
<span>{{ (displayName$ | async) }}</span>
|
||||
</button>
|
||||
|
@@ -0,0 +1,121 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AuthenticationService } from '@alfresco/adf-core';
|
||||
import { PeopleContentService } from '@alfresco/adf-content-services';
|
||||
import { UserInfoComponent } from './user-info.component';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('UserInfoComponent', () => {
|
||||
let component: UserInfoComponent;
|
||||
let fixture: ComponentFixture<UserInfoComponent>;
|
||||
let authServiceStub: Partial<AuthenticationService>;
|
||||
let peopleContentServiceStub: Partial<PeopleContentService>;
|
||||
let identityUserServiceStub: Partial<PeopleContentService>;
|
||||
|
||||
beforeEach(() => {
|
||||
authServiceStub = {
|
||||
isOauth: () => true,
|
||||
isECMProvider: () => true,
|
||||
isEcmLoggedIn: () => true,
|
||||
isKerberosEnabled: () => false,
|
||||
isLoggedIn: () => true
|
||||
};
|
||||
|
||||
peopleContentServiceStub = {
|
||||
getCurrentUserInfo: () =>
|
||||
of({
|
||||
firstName: 'John',
|
||||
email: 'john@example.com',
|
||||
id: 'johnDoe1',
|
||||
enabled: true,
|
||||
company: {
|
||||
organization: 'ABC Organization',
|
||||
address1: 'XYZ Road',
|
||||
address2: 'Ohio',
|
||||
address3: 'Westlake',
|
||||
postcode: '44145',
|
||||
telephone: '456876',
|
||||
fax: '323984',
|
||||
email: 'contact.us@abc.com'
|
||||
},
|
||||
isAdmin: () => true
|
||||
})
|
||||
};
|
||||
|
||||
identityUserServiceStub = {
|
||||
getCurrentUserInfo: () =>
|
||||
of({
|
||||
firstName: 'John',
|
||||
email: 'john@example.com',
|
||||
id: 'johnDoe1',
|
||||
enabled: true,
|
||||
company: {
|
||||
organization: 'ABC Organization',
|
||||
address1: 'XYZ Road',
|
||||
address2: 'Ohio',
|
||||
address3: 'Westlake',
|
||||
postcode: '44145',
|
||||
telephone: '456876',
|
||||
fax: '323984',
|
||||
email: 'contact.us@abc.com'
|
||||
},
|
||||
isAdmin: () => true
|
||||
})
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [UserInfoComponent],
|
||||
providers: [
|
||||
{ provide: AuthenticationService, useValue: authServiceStub },
|
||||
{ provide: PeopleContentService, useValue: peopleContentServiceStub },
|
||||
{ provide: identityUserServiceStub, useValue: identityUserServiceStub }
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should check if user is logged in', async () => {
|
||||
const loggedIn = component.isLoggedIn;
|
||||
expect(loggedIn).toBeTrue();
|
||||
});
|
||||
|
||||
it('should parse display name without email', async () => {
|
||||
const model = { firstName: 'John' };
|
||||
const displayName = component['parseDisplayName'](model);
|
||||
expect(displayName).toBe('John');
|
||||
});
|
||||
|
||||
it('should parse display name with email', async () => {
|
||||
const model = { firstName: 'John', email: 'john@example.com' };
|
||||
const displayName = component['parseDisplayName'](model);
|
||||
expect(displayName).toBe('John (john@example.com)');
|
||||
});
|
||||
});
|
@@ -22,21 +22,18 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { IdentityUserModel, IdentityUserService, AuthenticationService, UserInfoMode } from '@alfresco/adf-core';
|
||||
import { IdentityUserService, AuthenticationService } from '@alfresco/adf-core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
|
||||
import { PeopleContentService } from '@alfresco/adf-content-services';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-info',
|
||||
templateUrl: './user-info.component.html'
|
||||
})
|
||||
export class UserInfoComponent implements OnInit {
|
||||
mode: UserInfoMode;
|
||||
ecmUser$: Observable<EcmUserModel>;
|
||||
identityUser$: Observable<IdentityUserModel>;
|
||||
selectedIndex: number;
|
||||
userInfoMode = UserInfoMode;
|
||||
displayName$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private peopleContentService: PeopleContentService,
|
||||
@@ -51,15 +48,12 @@ export class UserInfoComponent implements OnInit {
|
||||
getUserInfo() {
|
||||
if (this.authService.isOauth()) {
|
||||
this.loadIdentityUserInfo();
|
||||
this.mode = UserInfoMode.SSO;
|
||||
|
||||
if (this.authService.isECMProvider() && this.authService.isEcmLoggedIn()) {
|
||||
this.mode = UserInfoMode.CONTENT_SSO;
|
||||
this.loadEcmUserInfo();
|
||||
}
|
||||
} else if (this.isEcmLoggedIn()) {
|
||||
this.loadEcmUserInfo();
|
||||
this.mode = UserInfoMode.CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +65,21 @@ export class UserInfoComponent implements OnInit {
|
||||
}
|
||||
|
||||
private loadEcmUserInfo(): void {
|
||||
this.ecmUser$ = this.peopleContentService.getCurrentUserInfo();
|
||||
this.displayName$ = this.peopleContentService.getCurrentUserInfo().pipe(map((model) => this.parseDisplayName(model)));
|
||||
}
|
||||
|
||||
private loadIdentityUserInfo() {
|
||||
this.identityUser$ = of(this.identityUserService.getCurrentUserInfo());
|
||||
this.displayName$ = of(this.identityUserService.getCurrentUserInfo()).pipe(map((model) => this.parseDisplayName(model)));
|
||||
}
|
||||
|
||||
private parseDisplayName(model: { firstName?: string; email?: string }): string {
|
||||
let result = model.firstName;
|
||||
|
||||
if (model.email) {
|
||||
result = `${model.firstName} (${model.email})`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private isEcmLoggedIn() {
|
||||
|
@@ -1,19 +0,0 @@
|
||||
<adf-layout-header
|
||||
[logo]="logo$ | async"
|
||||
[redirectUrl]="landingPage"
|
||||
[tooltip]="appName$ | async"
|
||||
[color]="headerColor$ | async"
|
||||
[title]="appName$ | async"
|
||||
(clicked)="onToggleSidenav($event)"
|
||||
[expandedSidenav]="isSidenavExpanded"
|
||||
>
|
||||
<div class="adf-toolbar--spacer adf-toolbar-divider"></div>
|
||||
|
||||
<aca-search-input *ngIf="isContentServiceEnabled()"></aca-search-input>
|
||||
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
|
||||
<ng-container *ngFor="let actionRef of actions; trackBy: trackByActionId">
|
||||
<aca-toolbar-action [actionRef]="actionRef"> </aca-toolbar-action>
|
||||
</ng-container>
|
||||
</adf-layout-header>
|
@@ -1,21 +0,0 @@
|
||||
.app-header {
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.02), 0 6px 10px 0 rgba(0, 0, 0, 0.014), 0 1px 18px 0 rgba(0, 0, 0, 0.012);
|
||||
z-index: 2;
|
||||
|
||||
adf-layout-header .mat-toolbar-single-row {
|
||||
color: var(--theme-header-text-color) !important;
|
||||
}
|
||||
|
||||
.mat-toolbar {
|
||||
background-image: var(--header-background-image) !important;
|
||||
background-repeat: no-repeat !important;
|
||||
|
||||
.aca-current-user {
|
||||
color: var(--theme-foreground-text-color) !important;
|
||||
}
|
||||
|
||||
.adf-toolbar-divider div {
|
||||
background-color: var(--theme-foreground-text-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,133 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppHeaderComponent } from './header.component';
|
||||
import { AppState } from '@alfresco/aca-shared/store';
|
||||
import { of } from 'rxjs';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ContentActionRef } from '@alfresco/adf-extensions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { AppExtensionService, SharedToolbarModule } from '@alfresco/aca-shared';
|
||||
import { CoreModule, SidenavLayoutComponent } from '@alfresco/adf-core';
|
||||
import { AppSearchInputModule } from '../search/search-input.module';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('AppHeaderComponent', () => {
|
||||
let component: AppHeaderComponent;
|
||||
let fixture: ComponentFixture<AppHeaderComponent>;
|
||||
|
||||
const actions = [
|
||||
{ id: 'action-1', type: 'button' },
|
||||
{ id: 'action-2', type: 'button' }
|
||||
] as Array<ContentActionRef>;
|
||||
|
||||
const store = {
|
||||
select: jasmine.createSpy('select'),
|
||||
dispatch: () => {}
|
||||
} as any;
|
||||
|
||||
const appExtensionService = {
|
||||
getHeaderActions: () => of(actions)
|
||||
} as any;
|
||||
|
||||
const app = {
|
||||
headerColor: 'some-color',
|
||||
headerTextColor: 'text-color',
|
||||
appName: 'name',
|
||||
logoPath: 'some/path'
|
||||
} as AppState;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, CoreModule.forChild(), AppSearchInputModule, SharedToolbarModule],
|
||||
declarations: [AppHeaderComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: AppExtensionService,
|
||||
useValue: appExtensionService
|
||||
},
|
||||
{
|
||||
provide: Store,
|
||||
useValue: store
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
store.select.and.callFake((memoizeFn) => of(memoizeFn({ app })));
|
||||
|
||||
fixture = TestBed.createComponent(AppHeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should set header color, header text color, name and logo', fakeAsync(() => {
|
||||
component.appName$.subscribe((val) => expect(val).toBe(app.appName));
|
||||
component.logo$.subscribe((val) => expect(val).toBe(app.logoPath));
|
||||
component.headerColor$.subscribe((val) => expect(val).toBe(app.headerColor));
|
||||
component.headerTextColor$.subscribe((val) => expect(val).toBe(app.headerTextColor));
|
||||
}));
|
||||
|
||||
it('should get header actions', fakeAsync(() => {
|
||||
component.ngOnInit();
|
||||
tick();
|
||||
expect(component.actions).toEqual(actions);
|
||||
}));
|
||||
|
||||
it('should minimize sidenav on toggle sidenav click', () => {
|
||||
const layout = TestBed.createComponent(SidenavLayoutComponent);
|
||||
const mockData: any = { layout: layout.componentInstance, isMenuMinimized: true };
|
||||
component.data = mockData;
|
||||
|
||||
const toggleMenuSpy = spyOn(component.data.layout, 'toggleMenu');
|
||||
component.onToggleSidenav(true);
|
||||
|
||||
expect(toggleMenuSpy).toHaveBeenCalled();
|
||||
expect(component.isSidenavExpanded).toBe(false);
|
||||
});
|
||||
|
||||
describe('Search input', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('should search be present when contentService is enabled', () => {
|
||||
fixture.detectChanges();
|
||||
const searchInput = fixture.debugElement.query(By.css('.aca-search-input'));
|
||||
|
||||
expect(searchInput).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should search not be present when contentService is disabled', () => {
|
||||
localStorage.setItem('contentService', 'false');
|
||||
fixture.detectChanges();
|
||||
const searchInput = fixture.debugElement.query(By.css('.aca-search-input'));
|
||||
|
||||
expect(searchInput).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,45 +0,0 @@
|
||||
<adf-upload-drag-area [rootFolderId]="currentFolderId" [disabled]="!canUpload">
|
||||
<adf-sidenav-layout
|
||||
#layout
|
||||
[sidenavMin]="70"
|
||||
[sidenavMax]="320"
|
||||
[stepOver]="600"
|
||||
[hideSidenav]="hideSidenav"
|
||||
[expandedSidenav]="expandedSidenav"
|
||||
(expanded)="onExpanded($event)"
|
||||
>
|
||||
<adf-sidenav-layout-header>
|
||||
<ng-template let-isMenuMinimized="isMenuMinimized">
|
||||
<app-header
|
||||
*ngIf="!hideSidenav"
|
||||
role="heading"
|
||||
aria-level="1"
|
||||
(toggleClicked)="layout.toggleMenu()"
|
||||
[data]="{ layout }"
|
||||
[expandedSidenav]="!isMenuMinimized()">
|
||||
</app-header>
|
||||
</ng-template>
|
||||
</adf-sidenav-layout-header>
|
||||
|
||||
<adf-sidenav-layout-navigation>
|
||||
<ng-template let-isMenuMinimized="isMenuMinimized">
|
||||
<app-sidenav
|
||||
[mode]="isMenuMinimized() ? 'collapsed' : 'expanded'"
|
||||
[attr.data-automation-id]="isMenuMinimized() ? 'collapsed' : 'expanded'"
|
||||
(swipeleft)="hideMenu($event)"
|
||||
>
|
||||
</app-sidenav>
|
||||
</ng-template>
|
||||
</adf-sidenav-layout-navigation>
|
||||
|
||||
<adf-sidenav-layout-content>
|
||||
<ng-template>
|
||||
<router-outlet></router-outlet>
|
||||
</ng-template>
|
||||
</adf-sidenav-layout-content>
|
||||
</adf-sidenav-layout>
|
||||
|
||||
<adf-file-uploading-dialog *ngIf="showFileUploadingDialog" position="left"></adf-file-uploading-dialog>
|
||||
</adf-upload-drag-area>
|
||||
|
||||
<router-outlet name="viewer"></router-outlet>
|
@@ -1,34 +0,0 @@
|
||||
.app-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
|
||||
router-outlet[name='viewer'] + * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
adf-file-uploading-dialog {
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 599px) {
|
||||
.adf-app-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 719px) {
|
||||
.adf-app-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
@@ -1,220 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppConfigService, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import { FileModel, UploadService } from '@alfresco/adf-content-services';
|
||||
import { AppLayoutComponent } from './app-layout.component';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore, SetSelectedNodesAction, ResetSelectionAction } from '@alfresco/aca-shared/store';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
class MockRouter {
|
||||
private url = 'some-url';
|
||||
private subject = new Subject();
|
||||
events = this.subject.asObservable();
|
||||
routerState = { snapshot: { url: this.url } };
|
||||
|
||||
navigateByUrl(url: string) {
|
||||
const navigationStart = new NavigationStart(0, url);
|
||||
this.subject.next(navigationStart);
|
||||
}
|
||||
}
|
||||
|
||||
describe('AppLayoutComponent', () => {
|
||||
let fixture: ComponentFixture<AppLayoutComponent>;
|
||||
let component: AppLayoutComponent;
|
||||
let appConfig: AppConfigService;
|
||||
let userPreference: UserPreferencesService;
|
||||
let store: Store<AppStore>;
|
||||
let router: Router;
|
||||
let uploadService: UploadService;
|
||||
let fakeFileList: FileModel[];
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
providers: [
|
||||
Store,
|
||||
{
|
||||
provide: Router,
|
||||
useClass: MockRouter
|
||||
}
|
||||
],
|
||||
declarations: [AppLayoutComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(AppLayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
appConfig = TestBed.inject(AppConfigService);
|
||||
store = TestBed.inject(Store);
|
||||
router = TestBed.inject(Router);
|
||||
userPreference = TestBed.inject(UserPreferencesService);
|
||||
|
||||
fakeFileList = [new FileModel(new File([], 'fakeFile'))];
|
||||
|
||||
uploadService = TestBed.inject(UploadService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appConfig.config.languages = [];
|
||||
appConfig.config.locale = 'en';
|
||||
});
|
||||
|
||||
describe('sidenav state', () => {
|
||||
it('should get state from configuration', () => {
|
||||
appConfig.config.sideNav = {
|
||||
expandedSidenav: false,
|
||||
preserveState: false
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
|
||||
it('should resolve state to true is no configuration', () => {
|
||||
appConfig.config.sidenav = {};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as true', () => {
|
||||
appConfig.config.sideNav = {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake((key) => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'true';
|
||||
}
|
||||
return 'false';
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as false', () => {
|
||||
appConfig.config.sidenav = {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake((key) => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'false';
|
||||
}
|
||||
return 'true';
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset selection before navigation', () => {
|
||||
const selection: any[] = [{ entry: { id: 'nodeId', name: 'name' } }];
|
||||
spyOn(store, 'dispatch').and.stub();
|
||||
fixture.detectChanges();
|
||||
store.dispatch(new SetSelectedNodesAction(selection));
|
||||
router.navigateByUrl('somewhere/over/the/rainbow');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(store.dispatch['calls'].mostRecent().args).toEqual([new ResetSelectionAction()]);
|
||||
});
|
||||
|
||||
it('should close menu on mobile screen size', () => {
|
||||
component.minimizeSidenav = false;
|
||||
component.layout.container = {
|
||||
isMobileScreenSize: true,
|
||||
toggleMenu: () => {}
|
||||
};
|
||||
|
||||
spyOn(component.layout.container, 'toggleMenu');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.hideMenu({ preventDefault: () => {} } as any);
|
||||
|
||||
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close menu on mobile screen size also when minimizeSidenav true', () => {
|
||||
fixture.detectChanges();
|
||||
component.minimizeSidenav = true;
|
||||
component.layout.container = {
|
||||
isMobileScreenSize: true,
|
||||
toggleMenu: () => {}
|
||||
};
|
||||
|
||||
spyOn(component.layout.container, 'toggleMenu');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.hideMenu({ preventDefault: () => {} } as any);
|
||||
|
||||
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('File Uploading Dialog', () => {
|
||||
it('should the uploading file dialog be visible on the left when the showFileUploadingDialog is true', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
uploadService.addToQueue(...fakeFileList);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const fileUploadingDialog = fixture.debugElement.query(By.css('adf-file-uploading-dialog'));
|
||||
|
||||
expect(fileUploadingDialog.attributes['position']).toEqual('left');
|
||||
expect(component.showFileUploadingDialog).toEqual(true);
|
||||
expect(fileUploadingDialog).not.toEqual(null);
|
||||
});
|
||||
|
||||
it('should the uploading file dialog not be visible when the showFileUploadingDialog is false', async () => {
|
||||
spyOn(store, 'select').and.returnValue(of(false));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
uploadService.addToQueue(...fakeFileList);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const fileUploadingDialog = fixture.debugElement.query(By.css('adf-file-uploading-dialog'));
|
||||
|
||||
expect(component.showFileUploadingDialog).toEqual(false);
|
||||
expect(fileUploadingDialog).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,176 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppConfigService, SidenavLayoutComponent, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { NavigationEnd, Router, NavigationStart } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import { filter, takeUntil, map, withLatestFrom, delay } from 'rxjs/operators';
|
||||
import { NodePermissionService } from '@alfresco/aca-shared';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { AppStore, getCurrentFolder, getFileUploadingDialog, ResetSelectionAction } from '@alfresco/aca-shared/store';
|
||||
import { Directionality } from '@angular/cdk/bidi';
|
||||
|
||||
@Component({
|
||||
selector: 'app-layout',
|
||||
templateUrl: './app-layout.component.html',
|
||||
styleUrls: ['./app-layout.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-layout' }
|
||||
})
|
||||
export class AppLayoutComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('layout', { static: true })
|
||||
layout: SidenavLayoutComponent;
|
||||
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
isSmallScreen$: Observable<boolean>;
|
||||
|
||||
expandedSidenav: boolean;
|
||||
currentFolderId: string;
|
||||
canUpload = false;
|
||||
|
||||
minimizeSidenav = false;
|
||||
hideSidenav = false;
|
||||
direction: Directionality;
|
||||
|
||||
showFileUploadingDialog: boolean;
|
||||
|
||||
private minimizeConditions: string[] = ['search'];
|
||||
private hideConditions: string[] = ['/preview/'];
|
||||
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
private permission: NodePermissionService,
|
||||
private router: Router,
|
||||
private userPreferenceService: UserPreferencesService,
|
||||
private appConfigService: AppConfigService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.isSmallScreen$ = this.breakpointObserver.observe(['(max-width: 600px)']).pipe(map((result) => result.matches));
|
||||
|
||||
this.hideSidenav = this.hideConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
|
||||
|
||||
this.minimizeSidenav = this.minimizeConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
|
||||
|
||||
if (!this.minimizeSidenav) {
|
||||
this.expandedSidenav = this.getSidenavState();
|
||||
} else {
|
||||
this.expandedSidenav = false;
|
||||
}
|
||||
|
||||
this.store
|
||||
.select(getCurrentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe((node) => {
|
||||
this.currentFolderId = node ? node.id : null;
|
||||
this.canUpload = node && this.permission.check(node, ['create']);
|
||||
});
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
withLatestFrom(this.isSmallScreen$),
|
||||
filter(([event, isSmallScreen]) => isSmallScreen && event instanceof NavigationEnd),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.layout.container.sidenav.close();
|
||||
});
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe((event: NavigationEnd) => {
|
||||
this.minimizeSidenav = this.minimizeConditions.some((el) => event.urlAfterRedirects.includes(el));
|
||||
this.hideSidenav = this.hideConditions.some((el) => event.urlAfterRedirects.includes(el));
|
||||
|
||||
this.updateState();
|
||||
});
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationStart),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.store.dispatch(new ResetSelectionAction());
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(getFileUploadingDialog)
|
||||
.pipe(delay(0), takeUntil(this.onDestroy$))
|
||||
.subscribe((fileUploadingDialog: boolean) => {
|
||||
this.showFileUploadingDialog = fileUploadingDialog;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
hideMenu(event: Event) {
|
||||
if (this.layout.container.isMobileScreenSize) {
|
||||
event.preventDefault();
|
||||
this.layout.container.toggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
if (this.minimizeSidenav && !this.layout.isMenuMinimized) {
|
||||
this.layout.isMenuMinimized = true;
|
||||
if (!this.layout.container.isMobileScreenSize) {
|
||||
this.layout.container.toggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.minimizeSidenav) {
|
||||
if (this.getSidenavState() && this.layout.isMenuMinimized) {
|
||||
this.layout.isMenuMinimized = false;
|
||||
this.layout.container.toggleMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExpanded(state: boolean) {
|
||||
if (!this.minimizeSidenav && this.appConfigService.get('sideNav.preserveState')) {
|
||||
this.userPreferenceService.set('expandedSidenav', state);
|
||||
}
|
||||
}
|
||||
|
||||
private getSidenavState(): boolean {
|
||||
const expand = this.appConfigService.get<boolean>('sideNav.expandedSidenav', true);
|
||||
const preserveState = this.appConfigService.get<boolean>('sideNav.preserveState', true);
|
||||
|
||||
if (preserveState) {
|
||||
return this.userPreferenceService.get('expandedSidenav', expand.toString()) === 'true';
|
||||
}
|
||||
|
||||
return expand;
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { AppLayoutComponent } from './app-layout/app-layout.component';
|
||||
import { ContentModule } from '@alfresco/adf-content-services';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppSidenavModule } from '../sidenav/sidenav.module';
|
||||
import { AppCommonModule } from '../common/common.module';
|
||||
import { AppHeaderModule } from '../header/header.module';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { PageLayoutModule } from '@alfresco/aca-shared';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
CoreModule.forChild(),
|
||||
ContentModule.forChild(),
|
||||
AppCommonModule,
|
||||
AppSidenavModule,
|
||||
AppHeaderModule,
|
||||
HttpClientModule,
|
||||
PageLayoutModule
|
||||
],
|
||||
declarations: [AppLayoutComponent],
|
||||
exports: [AppLayoutComponent, PageLayoutModule]
|
||||
})
|
||||
export class AppLayoutModule {}
|
@@ -1,30 +0,0 @@
|
||||
<ng-container *ngIf="(mainAction$ | async) as action">
|
||||
<ng-container [ngSwitch]="action.type">
|
||||
<ng-container *ngSwitchCase="actionTypes.button">
|
||||
<button
|
||||
*ngIf="expanded"
|
||||
mat-stroked-button
|
||||
[id]="action.id"
|
||||
(click)="runAction(action.actions.click)"
|
||||
[disabled]="action.disabled"
|
||||
class="app-main-action-button"
|
||||
data-automation-id="app-main-action-button"
|
||||
>
|
||||
{{action.title | translate}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="expanded === false"
|
||||
mat-icon-button
|
||||
[id]="action.id"
|
||||
(click)="runAction(action.actions.click)"
|
||||
[disabled]="action.disabled"
|
||||
data-automation-id="app-main-action-icon"
|
||||
title="{{ action.title| translate }}"
|
||||
>
|
||||
<mat-icon class="main-action-menu-icon">{{action.icon}}</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
@@ -1,10 +0,0 @@
|
||||
.app-main-action-button {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
background-color: var(--theme-accent-color);
|
||||
color: var(--theme-accent-color-default-contrast);
|
||||
}
|
||||
|
||||
.main-action-menu-icon {
|
||||
color: var(--theme-accent-color);
|
||||
}
|
@@ -1,156 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MainActionComponent } from './main-action.component';
|
||||
import { TranslationService, TranslationMock } from '@alfresco/adf-core';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { of } from 'rxjs';
|
||||
import { ACTION_CLICK, ACTION_TITLE, getContentActionRef } from '../../testing/content-action-ref';
|
||||
import { AppExtensionServiceMock } from '../../testing/app-extension-service-mock';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('MainActionComponent', () => {
|
||||
let mainActionComponent: MainActionComponent;
|
||||
const buttonQuery = '[data-automation-id="app-main-action-button"]';
|
||||
const iconQuery = '[data-automation-id="app-main-action-icon"]';
|
||||
|
||||
let fixture: ComponentFixture<MainActionComponent>;
|
||||
let appExtensionService: AppExtensionServiceMock;
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, MatButtonModule, TranslateModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: TranslationService, useClass: TranslationMock },
|
||||
{ provide: AppExtensionService, useClass: AppExtensionServiceMock }
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
appExtensionService = TestBed.inject(AppExtensionService);
|
||||
|
||||
fixture = TestBed.createComponent(MainActionComponent);
|
||||
mainActionComponent = fixture.componentInstance;
|
||||
});
|
||||
|
||||
describe('component is in expanded mode', () => {
|
||||
beforeEach(async () => {
|
||||
mainActionComponent.expanded = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display button if main action is configured', () => {
|
||||
const buttonMainAction = fixture.debugElement.nativeElement.querySelector(buttonQuery);
|
||||
const iconMainAction = fixture.debugElement.nativeElement.querySelector(iconQuery);
|
||||
|
||||
expect(iconMainAction).toBeFalsy();
|
||||
expect(buttonMainAction.textContent.trim()).toBe(ACTION_TITLE);
|
||||
});
|
||||
|
||||
it('should not display button if main action is not configured', () => {
|
||||
spyOn(appExtensionService, 'getMainAction').and.returnValue(of(undefined));
|
||||
mainActionComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.debugElement.nativeElement.querySelector(buttonQuery);
|
||||
expect(button).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call extension action', () => {
|
||||
const runExtensionActionSpy = spyOn(appExtensionService, 'runActionById');
|
||||
|
||||
const button = fixture.debugElement.nativeElement.querySelector(buttonQuery);
|
||||
button.click();
|
||||
|
||||
expect(runExtensionActionSpy).toHaveBeenCalledWith(ACTION_CLICK);
|
||||
});
|
||||
|
||||
it('should not call button if main action is disabled', () => {
|
||||
const disabledMainActionRef = getContentActionRef();
|
||||
disabledMainActionRef.disabled = true;
|
||||
|
||||
spyOn(appExtensionService, 'getMainAction').and.returnValue(of(disabledMainActionRef));
|
||||
const runAction = spyOn(mainActionComponent, 'runAction');
|
||||
|
||||
mainActionComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.debugElement.nativeElement.querySelector(buttonQuery);
|
||||
button.click();
|
||||
|
||||
expect(runAction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('component is displayed as icon', () => {
|
||||
beforeEach(async () => {
|
||||
mainActionComponent.expanded = false;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display icon if main action is configured', () => {
|
||||
const buttonMainAction = fixture.debugElement.nativeElement.querySelector(buttonQuery);
|
||||
const iconMainAction = fixture.debugElement.nativeElement.querySelector(iconQuery);
|
||||
|
||||
expect(buttonMainAction).toBeFalsy();
|
||||
expect(iconMainAction).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not display icon if main action is not configured', () => {
|
||||
spyOn(appExtensionService, 'getMainAction').and.returnValue(of(undefined));
|
||||
mainActionComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
const mainAction = fixture.debugElement.nativeElement.querySelector(iconQuery);
|
||||
expect(mainAction).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call extension action', () => {
|
||||
const runExtensionActionSpy = spyOn(appExtensionService, 'runActionById');
|
||||
|
||||
const mainAction = fixture.debugElement.nativeElement.querySelector(iconQuery);
|
||||
mainAction.click();
|
||||
|
||||
expect(runExtensionActionSpy).toHaveBeenCalledWith(ACTION_CLICK);
|
||||
});
|
||||
|
||||
it('should not call icon if main action is disabled', () => {
|
||||
const disabledMainActionRef = getContentActionRef();
|
||||
disabledMainActionRef.disabled = true;
|
||||
|
||||
spyOn(appExtensionService, 'getMainAction').and.returnValue(of(disabledMainActionRef));
|
||||
const runAction = spyOn(mainActionComponent, 'runAction');
|
||||
|
||||
mainActionComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.debugElement.nativeElement.querySelector(iconQuery);
|
||||
button.click();
|
||||
|
||||
expect(runAction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,58 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions';
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-action',
|
||||
templateUrl: './main-action.component.html',
|
||||
styleUrls: ['./main-action.component.scss']
|
||||
})
|
||||
export class MainActionComponent implements OnInit, OnDestroy {
|
||||
@Input() expanded: boolean;
|
||||
|
||||
mainAction$: Observable<ContentActionRef>;
|
||||
|
||||
actionTypes = ContentActionType;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(private extensions: AppExtensionService) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next(true);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.mainAction$ = this.extensions.getMainAction().pipe(takeUntil(this.onDestroy$));
|
||||
}
|
||||
|
||||
runAction(action: string): void {
|
||||
this.extensions.runActionById(action);
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MainActionComponent } from './main-action.component';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatButtonModule, MatIconModule, TranslateModule.forChild()],
|
||||
exports: [MainActionComponent],
|
||||
declarations: [MainActionComponent]
|
||||
})
|
||||
export class MainActionModule {}
|
@@ -22,6 +22,7 @@ $top-margin: 12px;
|
||||
font-size: 16px;
|
||||
padding-left: 15px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 12px !important;
|
||||
|
||||
.mat-form-field {
|
||||
font-size: 16px;
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<div
|
||||
class="app-search-container searchMenuTrigger"
|
||||
<div class="app-search-container searchMenuTrigger"
|
||||
[matMenuTriggerFor]="searchOptionsMenu"
|
||||
(menuOpened)="onMenuOpened()"
|
||||
(menuClosed)="syncInputValues()"
|
||||
>
|
||||
|
||||
<button mat-icon-button class="app-search-button" (click)="searchByOption()" [title]="'SEARCH.BUTTON.TOOLTIP' | translate">
|
||||
<mat-icon [attr.aria-label]="'SEARCH.BUTTON.ARIA-LABEL' | translate">search</mat-icon>
|
||||
</button>
|
||||
@@ -20,6 +20,10 @@
|
||||
<div matSuffix class="app-suffix-search-icon-wrapper">
|
||||
<mat-icon>arrow_drop_down</mat-icon>
|
||||
</div>
|
||||
|
||||
<button mat-icon-button matSuffix class="app-suffix-search-icon-wrapper app-close-icon" (click)="exitSearch()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
$search-width: 594px;
|
||||
$search-height: 40px;
|
||||
$search-height: 32px;
|
||||
$search-background: #f5f6f5;
|
||||
$search-border-radius: 4px;
|
||||
$top-margin: 12px;
|
||||
@@ -9,8 +9,26 @@ $top-margin: 12px;
|
||||
width: 100%;
|
||||
max-width: $search-width;
|
||||
height: $search-height + $top-margin;
|
||||
margin: 0 !important;
|
||||
|
||||
.app-search-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.app-input-form-field {
|
||||
.app-close-icon {
|
||||
height: 6px;
|
||||
|
||||
.mat-icon {
|
||||
font-size: 18px !important;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-input-element {
|
||||
caret-color: var(--theme-text-color);
|
||||
|
||||
@@ -52,6 +70,8 @@ mat-checkbox {
|
||||
background-color: $search-background;
|
||||
border-radius: $search-border-radius;
|
||||
height: $search-height;
|
||||
margin-bottom: 0 !important;
|
||||
padding-bottom: 26px !important;
|
||||
}
|
||||
|
||||
.app-search-control {
|
||||
|
@@ -31,12 +31,14 @@ import { SearchByTermAction, SearchActionTypes, SnackbarErrorAction, SnackbarAct
|
||||
import { AppHookService } from '@alfresco/aca-shared';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||
import { SearchNavigationService } from '../search-navigation.service';
|
||||
|
||||
describe('SearchInputComponent', () => {
|
||||
let fixture: ComponentFixture<SearchInputComponent>;
|
||||
let component: SearchInputComponent;
|
||||
let actions$: Actions;
|
||||
let appHookService: AppHookService;
|
||||
let searchInputService: SearchNavigationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -49,12 +51,20 @@ describe('SearchInputComponent', () => {
|
||||
actions$ = TestBed.inject(Actions);
|
||||
fixture = TestBed.createComponent(SearchInputComponent);
|
||||
appHookService = TestBed.inject(AppHookService);
|
||||
searchInputService = TestBed.inject(SearchNavigationService);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should change flag on library400Error event', () => {
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should change flag on library400Error event', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.has400LibraryError).toBe(false);
|
||||
|
||||
appHookService.library400Error.next();
|
||||
|
||||
expect(component.has400LibraryError).toBe(true);
|
||||
@@ -64,9 +74,13 @@ describe('SearchInputComponent', () => {
|
||||
expect(component.hasLibraryConstraint()).toBe(false);
|
||||
});
|
||||
|
||||
it('should have library constraint on 400 error received', () => {
|
||||
it('should have library constraint on 400 error received', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const libItem = component.searchOptions.find((item) => item.key.toLowerCase().indexOf('libraries') > 0);
|
||||
libItem.value = true;
|
||||
|
||||
appHookService.library400Error.next();
|
||||
|
||||
expect(component.hasLibraryConstraint()).toBe(true);
|
||||
@@ -192,4 +206,23 @@ describe('SearchInputComponent', () => {
|
||||
expect(component.isContentChecked()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exitSearch()', () => {
|
||||
it('should exit search on click of close icon', async () => {
|
||||
spyOn(component, 'exitSearch').and.callThrough();
|
||||
spyOn(searchInputService, 'navigateBack').and.callThrough();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const closeIcon = fixture.debugElement.nativeElement.querySelector('.app-close-icon');
|
||||
closeIcon.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.exitSearch).toHaveBeenCalled();
|
||||
expect(searchInputService.navigateBack).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,7 @@ import { Store } from '@ngrx/store';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { SearchInputControlComponent } from '../search-input-control/search-input-control.component';
|
||||
import { SearchNavigationService } from '../search-navigation.service';
|
||||
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
|
||||
|
||||
@Component({
|
||||
@@ -84,7 +85,8 @@ export class SearchInputComponent implements OnInit, OnDestroy {
|
||||
private config: AppConfigService,
|
||||
private router: Router,
|
||||
private store: Store<AppStore>,
|
||||
private appHookService: AppHookService
|
||||
private appHookService: AppHookService,
|
||||
public searchInputService: SearchNavigationService
|
||||
) {
|
||||
this.searchOnChange = this.config.get<boolean>('search.aca:triggeredOnChange', true);
|
||||
}
|
||||
@@ -106,6 +108,10 @@ export class SearchInputComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
exitSearch() {
|
||||
this.searchInputService.navigateBack();
|
||||
}
|
||||
|
||||
showInputValue() {
|
||||
this.has400LibraryError = false;
|
||||
this.searchedWord = this.getUrlSearchTerm();
|
||||
@@ -122,7 +128,9 @@ export class SearchInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onMenuOpened() {
|
||||
this.searchInputControl.searchInput.nativeElement.focus();
|
||||
if (this.searchInputControl) {
|
||||
this.searchInputControl.searchInput?.nativeElement?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +147,10 @@ export class SearchInputComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.EMPTY_SEARCH'));
|
||||
}
|
||||
this.trigger.closeMenu();
|
||||
|
||||
if (this.trigger) {
|
||||
this.trigger.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
onSearchChange(searchTerm: string) {
|
||||
|
@@ -0,0 +1,65 @@
|
||||
/*!
|
||||
* Copyright © 2005-2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchNavigationService } from './search-navigation.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('SearchNavigationService', () => {
|
||||
let service: SearchNavigationService;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), CoreModule.forRoot()]
|
||||
});
|
||||
service = TestBed.inject(SearchNavigationService);
|
||||
router = TestBed.inject(Router);
|
||||
});
|
||||
|
||||
it('should not navigate to saved route when exitSearch function is called if saved route is null', () => {
|
||||
const routerNavigate = spyOn(router, 'navigate');
|
||||
service.saveRoute('');
|
||||
service.navigateBack();
|
||||
|
||||
expect(routerNavigate).not.toHaveBeenCalledWith([service.previousRoute]);
|
||||
});
|
||||
|
||||
it('should navigate to saved route when exitSearch function is called', () => {
|
||||
const routerNavigate = spyOn(router, 'navigate');
|
||||
service.saveRoute('/personal-files');
|
||||
service.navigateBack();
|
||||
|
||||
expect(routerNavigate).toHaveBeenCalledWith([service.previousRoute]);
|
||||
});
|
||||
|
||||
it('should navigate to Search when navigateToSearch function is called', () => {
|
||||
const routerNavigate = spyOn(router, 'navigate');
|
||||
service.navigateToSearch();
|
||||
|
||||
expect(routerNavigate).toHaveBeenCalledWith(['/search']);
|
||||
});
|
||||
});
|
@@ -22,16 +22,33 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { AppHeaderComponent } from './header.component';
|
||||
import { AppSearchInputModule } from '../search/search-input.module';
|
||||
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CoreModule.forChild(), AppSearchInputModule, AppToolbarModule],
|
||||
declarations: [AppHeaderComponent],
|
||||
exports: [AppHeaderComponent]
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AppHeaderModule {}
|
||||
export class SearchNavigationService {
|
||||
private _previousRoute = '';
|
||||
|
||||
get previousRoute(): string {
|
||||
return this._previousRoute;
|
||||
}
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
saveRoute(route: string): void {
|
||||
this._previousRoute = route;
|
||||
}
|
||||
|
||||
navigateBack(): void {
|
||||
if (this.previousRoute) {
|
||||
this.router.navigate([this.previousRoute]);
|
||||
}
|
||||
}
|
||||
|
||||
navigateToSearch(): void {
|
||||
this.saveRoute(this.router.url);
|
||||
this.router.navigate(['/search']);
|
||||
}
|
||||
}
|
@@ -26,7 +26,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { ContentModule } from '@alfresco/adf-content-services';
|
||||
import { LockedByModule } from '@alfresco/aca-shared';
|
||||
import { LockedByModule, PageLayoutModule } from '@alfresco/aca-shared';
|
||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||
import { SearchResultsRowComponent } from './search-results-row/search-results-row.component';
|
||||
import { SearchLibrariesResultsComponent } from './search-libraries-results/search-libraries-results.component';
|
||||
@@ -34,10 +34,10 @@ import { AppInfoDrawerModule } from '../info-drawer/info.drawer.module';
|
||||
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
||||
import { AppCommonModule } from '../common/common.module';
|
||||
import { DirectivesModule } from '../../directives/directives.module';
|
||||
import { AppLayoutModule } from '../layout/layout.module';
|
||||
import { ContextMenuModule } from '../context-menu/context-menu.module';
|
||||
import { SearchActionMenuComponent } from './search-action-menu/search-action-menu.component';
|
||||
import { DocumentListCustomComponentsModule } from '../dl-custom-components/document-list-custom-components.module';
|
||||
import { AppSearchInputModule } from './search-input.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -48,10 +48,11 @@ import { DocumentListCustomComponentsModule } from '../dl-custom-components/docu
|
||||
AppInfoDrawerModule,
|
||||
AppToolbarModule,
|
||||
DirectivesModule,
|
||||
AppLayoutModule,
|
||||
PageLayoutModule,
|
||||
ContextMenuModule,
|
||||
LockedByModule,
|
||||
DocumentListCustomComponentsModule
|
||||
DocumentListCustomComponentsModule,
|
||||
AppSearchInputModule
|
||||
],
|
||||
declarations: [SearchResultsComponent, SearchLibrariesResultsComponent, SearchResultsRowComponent, SearchActionMenuComponent],
|
||||
exports: [SearchResultsComponent, SearchLibrariesResultsComponent, SearchResultsRowComponent, SearchActionMenuComponent]
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<aca-page-layout>
|
||||
<aca-page-layout-header>
|
||||
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE"> </adf-breadcrumb>
|
||||
<aca-search-input></aca-search-input>
|
||||
<div class="adf-toolbar--spacer adf-toolbar-divider"></div>
|
||||
<adf-toolbar class="adf-toolbar--inline">
|
||||
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>
|
||||
|
@@ -24,7 +24,6 @@
|
||||
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { SearchResultsComponent } from './search-results.component';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { AppSearchResultsModule } from '../search-results.module';
|
||||
import { AppConfigService, CoreModule, TranslationService } from '@alfresco/adf-core';
|
||||
import { Store } from '@ngrx/store';
|
||||
@@ -33,7 +32,9 @@ import { Pagination, SearchRequest } from '@alfresco/js-api';
|
||||
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { AppService } from '@alfresco/aca-shared';
|
||||
|
||||
describe('SearchComponent', () => {
|
||||
let component: SearchResultsComponent;
|
||||
@@ -49,8 +50,15 @@ describe('SearchComponent', () => {
|
||||
beforeEach(() => {
|
||||
params = new BehaviorSubject({ q: 'TYPE: "cm:folder" AND %28=cm: name: email OR cm: name: budget%29' });
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), CoreModule.forRoot(), AppTestingModule, AppSearchResultsModule],
|
||||
imports: [TranslateModule.forRoot(), AppTestingModule, CoreModule.forRoot(), AppSearchResultsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: AppService,
|
||||
useValue: {
|
||||
appNavNarMode$: new BehaviorSubject('expanded'),
|
||||
toggleAppNavBar$: new Subject()
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
|
@@ -27,9 +27,6 @@
|
||||
<mat-panel-title>
|
||||
<div class="item">
|
||||
<button
|
||||
[ngClass]="{
|
||||
'action-button--active': acaExpansionPanel.hasActiveLinks()
|
||||
}"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[id]="item.id"
|
||||
[attr.title]="item.description | translate"
|
||||
|
@@ -0,0 +1,20 @@
|
||||
<div class="sidenav-header">
|
||||
<div class="sidenav-header-title">
|
||||
<div class="sidenav-header-title-logo"
|
||||
(click)="toggleNavBar.emit()"
|
||||
(keypress)="toggleNavBar.emit()">
|
||||
<img
|
||||
src="{{ logo$ | async }}"
|
||||
title="{{'APP.TOOLTIPS.COLLAPSE_NAVIGATION' | translate}}"
|
||||
alt="{{ 'CORE.HEADER.LOGO_ARIA' | translate }}" />
|
||||
</div>
|
||||
|
||||
<div class="sidenav-header-title-text" [routerLink]="landingPage">
|
||||
{{ appName$ | async | translate }}
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let actionRef of actions; trackBy: trackByActionId">
|
||||
<aca-toolbar-action [actionRef]="actionRef"></aca-toolbar-action>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
@@ -22,55 +22,36 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, Output, EventEmitter, OnInit, Input, OnDestroy } from '@angular/core';
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { AppStore, getAppName, getLogoPath } from '@alfresco/aca-shared/store';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { ContentActionRef } from '@alfresco/adf-extensions';
|
||||
import { AppStore, getHeaderColor, getAppName, getLogoPath, getHeaderImagePath, getHeaderTextColor } from '@alfresco/aca-shared/store';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { AppConfigService, SidenavLayoutComponent } from '@alfresco/adf-core';
|
||||
import { isContentServiceEnabled } from '@alfresco/aca-shared/rules';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
selector: 'app-sidenav-header',
|
||||
templateUrl: `./sidenav-header.component.html`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-header' }
|
||||
host: { class: 'app-sidenav-header' }
|
||||
})
|
||||
export class AppHeaderComponent implements OnInit, OnDestroy {
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
@Output()
|
||||
toggleClicked = new EventEmitter();
|
||||
|
||||
@Input() expandedSidenav = true;
|
||||
|
||||
@Input() data: { layout?: SidenavLayoutComponent; isMenuMinimized?: boolean } = {};
|
||||
|
||||
get isSidenavExpanded(): boolean {
|
||||
return !this.data.isMenuMinimized ?? this.expandedSidenav;
|
||||
}
|
||||
export class SidenavHeaderComponent implements OnInit, OnDestroy {
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
appName$: Observable<string>;
|
||||
headerColor$: Observable<any>;
|
||||
headerTextColor$: Observable<string>;
|
||||
logo$: Observable<string>;
|
||||
landingPage: string;
|
||||
|
||||
actions: Array<ContentActionRef> = [];
|
||||
|
||||
constructor(public store: Store<AppStore>, private appExtensions: AppExtensionService, private appConfigService: AppConfigService) {
|
||||
this.headerColor$ = store.select(getHeaderColor);
|
||||
this.headerTextColor$ = store.select(getHeaderTextColor);
|
||||
@Output()
|
||||
toggleNavBar = new EventEmitter();
|
||||
|
||||
constructor(public store: Store<AppStore>, private appConfigService: AppConfigService, private appExtensions: AppExtensionService) {
|
||||
this.appName$ = store.select(getAppName);
|
||||
this.logo$ = store.select(getLogoPath);
|
||||
this.landingPage = this.appConfigService.get('landingPage', '/personal-files');
|
||||
|
||||
store.select(getHeaderImagePath).subscribe((path) => {
|
||||
document.body.style.setProperty('--header-background-image', `url('${path}')`);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -80,18 +61,6 @@ export class AppHeaderComponent implements OnInit, OnDestroy {
|
||||
.subscribe((actions) => {
|
||||
this.actions = actions;
|
||||
});
|
||||
|
||||
this.headerTextColor$.subscribe((color) => {
|
||||
document.documentElement.style.setProperty('--adf-header-text-color', color);
|
||||
});
|
||||
}
|
||||
|
||||
onToggleSidenav(_event: boolean): void {
|
||||
this.data.layout.toggleMenu();
|
||||
}
|
||||
|
||||
isContentServiceEnabled(): boolean {
|
||||
return isContentServiceEnabled();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
@@ -1,3 +0,0 @@
|
||||
<app-sidenav
|
||||
[mode]="data.mode"
|
||||
></app-sidenav>
|
@@ -1,36 +1,16 @@
|
||||
<div class="sidenav">
|
||||
<ng-container [ngSwitch]="mode">
|
||||
<div class="section action-menu" [ngClass]="'section--' + mode">
|
||||
<app-main-action [expanded]="mode === 'expanded'"></app-main-action>
|
||||
<app-create-menu [expanded]="mode === 'expanded'"></app-create-menu>
|
||||
</div>
|
||||
<app-sidenav-header (toggleNavBar)="toggleClick()"></app-sidenav-header>
|
||||
|
||||
<div class="section-sub-actions">
|
||||
<div *ngFor="let group of groups; trackBy: trackByGroupId" class="section" [ngClass]="'section--' + mode">
|
||||
<ng-container *ngSwitchCase="'expanded'">
|
||||
<div *ngFor="let group of groups; trackBy: trackByGroupId" class="section">
|
||||
<mat-list-item *ngFor="let item of group.items; trackBy: trackByLinkId">
|
||||
<ng-container *ngIf="!item.component">
|
||||
<app-expand-menu [item]="item"></app-expand-menu>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.component">
|
||||
<adf-dynamic-component [data]="{ item: item, state: 'expanded' }" [id]="item.component"></adf-dynamic-component>
|
||||
</ng-container>
|
||||
</mat-list-item>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'collapsed'">
|
||||
<div class="list-item" *ngFor="let item of group.items; trackBy: trackByLinkId">
|
||||
<ng-container *ngIf="!item.component">
|
||||
<app-button-menu [item]="item"></app-button-menu>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.component">
|
||||
<adf-dynamic-component [data]="{ item: item, state: 'collapsed' }" [id]="item.component"> </adf-dynamic-component>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@@ -1,151 +1,129 @@
|
||||
.app-sidenav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: var(--theme-background-color);
|
||||
overflow-y: hidden;
|
||||
background: var(--theme-sidenav-background-color);
|
||||
|
||||
.action-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
}
|
||||
&-header {
|
||||
padding: 32px 0;
|
||||
|
||||
.section.action-menu {
|
||||
padding: 8px 14px;
|
||||
position: sticky;
|
||||
}
|
||||
&-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding: 0 24px;
|
||||
|
||||
.section-sub-actions {
|
||||
overflow-y: auto;
|
||||
}
|
||||
&-logo {
|
||||
img {
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 8px 6px;
|
||||
border-bottom: 1px solid var(--theme-divider-color);
|
||||
&-text {
|
||||
flex: 1;
|
||||
color: var(--theme-selected-text-color);
|
||||
padding-left: 32px;
|
||||
letter-spacing: 0.25px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: var(--theme-body-1-font-size);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.section--collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.section-sub-actions {
|
||||
overflow-y: auto;
|
||||
|
||||
.list-item {
|
||||
padding: 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
}
|
||||
.mat-expansion-panel {
|
||||
width: 100%;
|
||||
background-color: unset;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
&-header {
|
||||
height: 32px;
|
||||
padding: 0 32px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&-header:hover {
|
||||
background: var(--theme-hover-background-color);
|
||||
}
|
||||
|
||||
.full-width {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
&-header-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-button--active {
|
||||
color: var(--theme-primary-color) !important;
|
||||
}
|
||||
&-body {
|
||||
padding: 0 0 16px;
|
||||
font-size: var(--theme-body-1-font-size);
|
||||
|
||||
.action-button {
|
||||
color: var(--theme-text-color);
|
||||
}
|
||||
.mat-button {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button .action-button__label {
|
||||
margin: 0 8px !important;
|
||||
}
|
||||
.mat-expansion-indicator {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 12px 0;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
.mat-expansion-indicator::after {
|
||||
transform: rotate(226deg);
|
||||
}
|
||||
}
|
||||
|
||||
.app-item,
|
||||
.app-item .item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
.item {
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
|
||||
.item:hover .action-button__label {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
&:hover .action-button__label {
|
||||
color: var(--theme-selected-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0 8px 0 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.action-button {
|
||||
color: var(--theme-action-button-text-color);
|
||||
height: 32px;
|
||||
padding: 0 24px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel {
|
||||
width: 100%;
|
||||
background-color: unset;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.full-width {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled='true']):hover {
|
||||
background: none !important;
|
||||
}
|
||||
.action-button--active {
|
||||
color: var(--theme-selected-text-color) !important;
|
||||
background: var(--theme-selected-background-color);
|
||||
}
|
||||
|
||||
.mat-expansion-indicator {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.aca-menu-panel {
|
||||
.action-button--active {
|
||||
color: var(--theme-accent-color) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: var(--theme-primary-color);
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
color: var(--theme-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
[dir='rtl'] .sidenav {
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0 0 0 8px !important;
|
||||
.action-panel-header {
|
||||
color: var(--theme-action-button-text-color);
|
||||
padding: 0 24px;
|
||||
|
||||
&__label {
|
||||
font-size: var(--theme-caption-font-size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { AppExtensionService, AppService } from '@alfresco/aca-shared';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
|
||||
describe('SidenavComponent', () => {
|
||||
let fixture: ComponentFixture<SidenavComponent>;
|
||||
@@ -37,6 +38,15 @@ describe('SidenavComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [SidenavComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: AppService,
|
||||
useValue: {
|
||||
appNavNarMode$: new BehaviorSubject('expanded'),
|
||||
toggleAppNavBar$: new Subject()
|
||||
}
|
||||
}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
|
@@ -28,7 +28,8 @@ import { Store } from '@ngrx/store';
|
||||
import { AppStore, getSideNavState } from '@alfresco/aca-shared/store';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { AppExtensionService, AppService } from '@alfresco/aca-shared';
|
||||
import { SidenavLayoutComponent } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidenav',
|
||||
@@ -39,12 +40,15 @@ import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
})
|
||||
export class SidenavComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
mode: 'collapsed' | 'expanded' = 'expanded';
|
||||
data: {
|
||||
layout?: SidenavLayoutComponent;
|
||||
mode?: 'collapsed' | 'expanded';
|
||||
} = {};
|
||||
|
||||
groups: Array<NavBarGroupRef> = [];
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(private store: Store<AppStore>, private extensions: AppExtensionService) {}
|
||||
constructor(private store: Store<AppStore>, private extensions: AppExtensionService, private appService: AppService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.store
|
||||
@@ -53,6 +57,9 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
||||
.subscribe(() => {
|
||||
this.groups = this.extensions.getApplicationNavigation(this.extensions.navbar);
|
||||
});
|
||||
|
||||
this.appService.appNavNarMode$.next(this.data.mode);
|
||||
this.appService.toggleAppNavBar$.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.toggleNavBar());
|
||||
}
|
||||
|
||||
trackByGroupId(_: number, obj: NavBarGroupRef): string {
|
||||
@@ -63,6 +70,15 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
||||
return obj.id;
|
||||
}
|
||||
|
||||
toggleClick() {
|
||||
this.toggleNavBar();
|
||||
}
|
||||
|
||||
private toggleNavBar() {
|
||||
this.data.layout.toggleMenu();
|
||||
this.appService.appNavNarMode$.next(this.data.layout.isMenuMinimized ? 'collapsed' : 'expanded');
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
|
@@ -24,11 +24,9 @@
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AppCreateMenuModule } from '../create-menu/create-menu.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
|
||||
import { ExpansionPanelDirective } from './directives/expansion-panel.directive';
|
||||
import { MenuPanelDirective } from './directives/menu-panel.directive';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
@@ -36,19 +34,11 @@ import { ActiveLinkDirective } from './directives/active-link.directive';
|
||||
import { ExpandMenuComponent } from './components/expand-menu.component';
|
||||
import { ButtonMenuComponent } from './components/button-menu.component';
|
||||
import { ActionDirective } from './directives/action.directive';
|
||||
import { MainActionModule } from '../main-action/main-action.module';
|
||||
import { SidenavWrapperComponent } from './sidenav-wrapper/sidenav-wrapper.component';
|
||||
import { SidenavHeaderComponent } from './components/sidenav-header.component';
|
||||
import { SharedToolbarModule } from '@alfresco/aca-shared';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CoreModule.forChild(),
|
||||
CoreExtensionsModule.forChild(),
|
||||
ExtensionsModule.forChild(),
|
||||
RouterModule,
|
||||
AppCreateMenuModule,
|
||||
MainActionModule
|
||||
],
|
||||
imports: [CoreModule.forChild(), ExtensionsModule.forChild(), RouterModule, AppCreateMenuModule, SharedToolbarModule],
|
||||
declarations: [
|
||||
MenuPanelDirective,
|
||||
ExpansionPanelDirective,
|
||||
@@ -57,7 +47,7 @@ import { SidenavWrapperComponent } from './sidenav-wrapper/sidenav-wrapper.compo
|
||||
ExpandMenuComponent,
|
||||
ButtonMenuComponent,
|
||||
SidenavComponent,
|
||||
SidenavWrapperComponent
|
||||
SidenavHeaderComponent
|
||||
],
|
||||
exports: [
|
||||
MenuPanelDirective,
|
||||
|
@@ -31,7 +31,8 @@ import { AppCommonModule } from '../common/common.module';
|
||||
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
||||
import { DirectivesModule } from '../../directives/directives.module';
|
||||
import { ContextMenuModule } from '../context-menu/context-menu.module';
|
||||
import { AppLayoutModule } from '../layout/layout.module';
|
||||
import { AppSearchInputModule } from '../search/search-input.module';
|
||||
import { PageLayoutModule } from '@alfresco/aca-shared';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -42,7 +43,8 @@ import { AppLayoutModule } from '../layout/layout.module';
|
||||
AppCommonModule,
|
||||
AppToolbarModule,
|
||||
ContextMenuModule,
|
||||
AppLayoutModule
|
||||
PageLayoutModule,
|
||||
AppSearchInputModule
|
||||
],
|
||||
declarations: [TrashcanComponent],
|
||||
exports: [TrashcanComponent]
|
||||
|
@@ -139,4 +139,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -185,4 +185,4 @@ app-view-profile {
|
||||
padding-top: 2rem;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1089,7 +1089,7 @@ export class ContentManagementService {
|
||||
|
||||
private focusAfterClose(focusedElementSelector: string): void {
|
||||
if (focusedElementSelector) {
|
||||
document.querySelector<HTMLElement>(focusedElementSelector).focus();
|
||||
document.querySelector<HTMLElement>(focusedElementSelector)?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -700,7 +700,7 @@ export class NodeActionsService {
|
||||
|
||||
private focusAfterClose(focusedElementSelector: string): void {
|
||||
if (focusedElementSelector) {
|
||||
document.querySelector<HTMLElement>(focusedElementSelector).focus();
|
||||
document.querySelector<HTMLElement>(focusedElementSelector)?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -164,7 +164,7 @@ export class DownloadEffects {
|
||||
|
||||
private focusAfterClose(focusedElementSelector: string): void {
|
||||
if (focusedElementSelector) {
|
||||
document.querySelector<HTMLElement>(focusedElementSelector).focus();
|
||||
document.querySelector<HTMLElement>(focusedElementSelector)?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ import { SearchEffects } from './search.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Router } from '@angular/router';
|
||||
import { SearchOptionIds, SearchByTermAction } from '@alfresco/aca-shared/store';
|
||||
import { SearchOptionIds, SearchByTermAction, SearchAction } from '@alfresco/aca-shared/store';
|
||||
|
||||
describe('SearchEffects', () => {
|
||||
let store: Store<any>;
|
||||
@@ -77,4 +77,15 @@ describe('SearchEffects', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/search;q=%2528test%2529');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('search$', () => {
|
||||
it('should navigate to search when the toolbar search icon is clicked', fakeAsync(() => {
|
||||
const routerNavigate = spyOn(router, 'navigate');
|
||||
store.dispatch(new SearchAction());
|
||||
|
||||
tick();
|
||||
|
||||
expect(routerNavigate).toHaveBeenCalledWith(['/search']);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -25,12 +25,24 @@
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SearchActionTypes, SearchByTermAction, SearchOptionIds } from '@alfresco/aca-shared/store';
|
||||
import { SearchAction, SearchActionTypes, SearchByTermAction, SearchOptionIds } from '@alfresco/aca-shared/store';
|
||||
import { Router } from '@angular/router';
|
||||
import { SearchNavigationService } from '../../components/search/search-navigation.service';
|
||||
|
||||
@Injectable()
|
||||
export class SearchEffects {
|
||||
constructor(private actions$: Actions, private router: Router) {}
|
||||
constructor(private actions$: Actions, private router: Router, private searchNavigationService: SearchNavigationService) {}
|
||||
|
||||
search$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<SearchAction>(SearchActionTypes.Search),
|
||||
map(() => {
|
||||
this.searchNavigationService.navigateToSearch();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
searchByTerm$ = createEffect(
|
||||
() =>
|
||||
|
@@ -26,10 +26,7 @@ import { AppState, AppStore } from '@alfresco/aca-shared/store';
|
||||
|
||||
export const INITIAL_APP_STATE: AppState = {
|
||||
appName: 'Alfresco Content Application',
|
||||
headerColor: '#ffffff',
|
||||
headerTextColor: '#000000',
|
||||
logoPath: 'assets/images/alfresco-logo-white.svg',
|
||||
headerImagePath: 'assets/images/mastHead-bg-shapesPattern.svg',
|
||||
customCssPath: '',
|
||||
webFontPath: '',
|
||||
sharedUrl: '',
|
||||
|
@@ -34,7 +34,6 @@ import {
|
||||
SetRepositoryInfoAction,
|
||||
SetInfoDrawerStateAction,
|
||||
SetInfoDrawerMetadataAspectAction,
|
||||
SetHeaderColorAction,
|
||||
SetCurrentNodeVersionAction,
|
||||
SetFileUploadingDialogAction,
|
||||
SetInfoDrawerPreviewStateAction,
|
||||
@@ -50,12 +49,6 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action):
|
||||
case AppActionTypes.SetInitialState:
|
||||
newState = Object.assign({}, (action as SetInitialStateAction).payload);
|
||||
break;
|
||||
case AppActionTypes.SetHeaderColor:
|
||||
newState = {
|
||||
...state,
|
||||
headerColor: (action as SetHeaderColorAction).color
|
||||
};
|
||||
break;
|
||||
case NodeActionTypes.SetSelection:
|
||||
newState = updateSelectedNodes(state, action as SetSelectedNodesAction);
|
||||
break;
|
||||
|
@@ -48,6 +48,6 @@ ng-component {
|
||||
adf-layout-container,
|
||||
aca-search-results,
|
||||
ng-component {
|
||||
height: 80vh;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,11 @@ $datetimepicker-selected-date-background: #2254b2;
|
||||
$datetimepicker-cell-background-color: #fff;
|
||||
$datetimepicker-cell-focus-border-color: #1f74db;
|
||||
$datetimepicker-cell-focus-background-color: rgba(33, 33, 33, 0.12);
|
||||
$sidenav-background-color: #f8f8f8;
|
||||
$selected-text-color: #212121;
|
||||
$selected-background-color: rgba(31, 116, 219, 0.24);
|
||||
$action-button-text-color: rgba(33, 35, 40, 0.7);
|
||||
$page-layout-header-background-color: #fff;
|
||||
|
||||
// CSS Variables
|
||||
$defaults: (
|
||||
@@ -44,7 +49,6 @@ $defaults: (
|
||||
--theme-title-color: mat.get-color-from-palette($foreground, text, 0.87),
|
||||
--theme-text-disabled-color: mat.get-color-from-palette($foreground, text, 0.38),
|
||||
--theme-border-color: mat.get-color-from-palette($foreground, text, 0.07),
|
||||
--header-background-image: url('/assets/images/mastHead-bg-shapesPattern.svg'),
|
||||
--theme-card-background-color: mat.get-color-from-palette($background, card),
|
||||
--theme-foreground-text-color: mat.get-color-from-palette($foreground, text, 0.72),
|
||||
--theme-foreground-text-bold-color: mat.get-color-from-palette($foreground, text, 0.87),
|
||||
@@ -68,7 +72,14 @@ $defaults: (
|
||||
--theme-datetimepicker-selected-date-background: $datetimepicker-selected-date-background,
|
||||
--theme-datetimepicker-cell-background: $datetimepicker-cell-background-color,
|
||||
--theme-datetimepicker-cell-focus-border: $datetimepicker-cell-focus-border-color,
|
||||
--theme-datetimepicker-cell-focus-background: $datetimepicker-cell-focus-background-color
|
||||
--theme-datetimepicker-cell-focus-background: $datetimepicker-cell-focus-background-color,
|
||||
--theme-sidenav-background-color: $sidenav-background-color,
|
||||
--theme-selected-text-color: $selected-text-color,
|
||||
--theme-selected-background-color: $selected-background-color,
|
||||
--theme-hover-background-color: $grey-text-background,
|
||||
--theme-action-button-text-color: $action-button-text-color,
|
||||
--theme-header-border-color: $grey-background,
|
||||
--theme-page-layout-header-background-color: $page-layout-header-background-color,
|
||||
);
|
||||
|
||||
// propagates SCSS variables into the CSS variables scope
|
||||
|
Reference in New Issue
Block a user