added header and search layout changes

This commit is contained in:
SheenaMalhotra182
2023-02-08 12:50:39 +05:30
committed by Yasa-Nataliya
parent d4cf1eddcc
commit 5b39510106
30 changed files with 731 additions and 80 deletions

View File

@@ -132,44 +132,6 @@
}
],
"create": [
{
"id": "app.create.uploadFile",
"order": 100,
"icon": "file_upload",
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE",
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES",
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES_NOT_ALLOWED",
"actions": {
"click": "UPLOAD_FILES"
},
"rules": {
"enabled": "app.navigation.folder.canUpload",
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.uploadFolder",
"order": 200,
"icon": "file_upload",
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER",
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS",
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS_NOT_ALLOWED",
"actions": {
"click": "UPLOAD_FOLDER"
},
"rules": {
"enabled": "app.navigation.folder.canUpload",
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.separator.1",
"type": "separator",
"order": 300,
"rules": {
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.folder",
"order": 400,
@@ -198,14 +160,6 @@
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.separator.2",
"type": "separator",
"order": 650,
"rules": {
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.fileFromTemplate",
"order": 700,
@@ -237,6 +191,38 @@
}
}
],
"upload": [
{
"id": "app.create.uploadFile",
"order": 100,
"icon": "file_upload",
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE",
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES",
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES_NOT_ALLOWED",
"actions": {
"click": "UPLOAD_FILES"
},
"rules": {
"enabled": "app.navigation.folder.canUpload",
"visible": "app.isContentServiceEnabled"
}
},
{
"id": "app.create.uploadFolder",
"order": 200,
"icon": "file_upload",
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER",
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS",
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS_NOT_ALLOWED",
"actions": {
"click": "UPLOAD_FOLDER"
},
"rules": {
"enabled": "app.navigation.folder.canUpload",
"visible": "app.isContentServiceEnabled"
}
}
],
"navbar": [
{
"id": "app.navbar.primary",

View File

@@ -103,6 +103,12 @@
"CREATE_LIBRARY": "Create a new File Library"
}
},
"HEADER": {
"BUTTONS": {
"CREATE": "Create",
"UPLOAD": "Upload"
}
},
"BROWSE": {
"FILE": {
"TITLE": "Files",

View File

@@ -122,6 +122,7 @@ import { AcaFolderRulesModule } from '@alfresco/aca-folder-rules';
import { TagsColumnComponent } from './components/dl-custom-components/tags-column/tags-column.component';
import { UserInfoComponent } from './components/common/user-info/user-info.component';
import { CustomIconsModule } from './extensions/custom-icons.module';
import { AppHeaderActionsModule } from './components/header-actions/header-actions.module';
registerLocaleData(localeFr);
registerLocaleData(localeDe);
@@ -163,6 +164,7 @@ registerLocaleData(localeSv);
AppCreateMenuModule,
DocumentListCustomComponentsModule,
AppSearchInputModule,
AppHeaderActionsModule,
AppSearchResultsModule,
AppHeaderModule,
AppNodeVersionModule,

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.LIBRARIES.MENU.FAVORITE_LIBRARIES.TITLE"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.FAVORITES.TITLE"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -1,7 +1,7 @@
<aca-page-layout [hasError]="!isValidPath">
<aca-page-layout-header>
<adf-breadcrumb [root]="title" [folderNode]="node" [maxItems]="isSmallScreen ? 1 : 0" (navigate)="onBreadcrumbNavigate($event)"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -0,0 +1,33 @@
<div>
<div class="adf-toolbar--spacer adf-toolbar-divider"></div>
<span class="action-bar">
<button class="aca-mat-button aca-create-button" mat-stroked-button data-automation-id="create-button"
*ngIf="canShowCreateButton()"
[matMenuTriggerFor]="createMenu">
{{ 'APP.HEADER.BUTTONS.CREATE' | translate }}
</button>
<mat-menu #createMenu="matMenu" role="menu" class="app-create-menu__root-menu app-create-menu__sub-menu"
[overlapTrigger]="false" yPosition="below">
<div *ngFor="let action of createActions; trackBy: trackByActionId">
<app-toolbar-menu-item [actionRef]="action"></app-toolbar-menu-item>
</div>
</mat-menu>
<button class="aca-mat-button aca-upload-button" mat-stroked-button data-automation-id="upload-button"
*ngIf="canShowUploadButton()" [matMenuTriggerFor]="uploadMenu">
{{ 'APP.HEADER.BUTTONS.UPLOAD' | translate }}
</button>
<mat-menu #uploadMenu="matMenu" role="menu" class="app-upload-menu__root-menu app-upload-menu__sub-menu"
[overlapTrigger]="false" yPosition="below">
<div *ngFor="let action of uploadActions; trackBy: trackByActionId">
<app-toolbar-menu-item [actionRef]="action"></app-toolbar-menu-item>
</div>
</mat-menu>
<app-main-action *ngIf="isTasksRoute() || isProcessesRoute()"></app-main-action>
<adf-toolbar-divider *ngIf="canShowSearchSeparator()">
</adf-toolbar-divider>
<aca-search-input class="app-search-input"></aca-search-input>
</span>
</div>

View File

@@ -0,0 +1,80 @@
.aca-page-layout-header {
// display: flex;
// align-items: center;
// flex: 0 0 65px;
// flex-basis: 96px;
// background: var(--theme-page-layout-header-background-color);
// border-bottom: 1px solid var(--theme-border-color, rgba(0, 0, 0, 0.07));
// padding: 0 24px;
.adf-breadcrumb-item {
font-size: 20px !important;
font-weight: 400 !important;
letter-spacing: 0.15px !important;
}
.app-search-input {
background: none;
}
.action-bar {
display: flex;
flex: auto;
height: 32px;
margin-left: 24px;
adf-toolbar-divider {
width: 24px !important;
height: 32px !important;
margin: 4px 0 0 12px !important;
}
}
.aca-mat-button {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
letter-spacing: 0.25px;
font-weight: 500;
font-size: 14px;
font-style: normal;
border-radius: 6px;
border: none;
}
.aca-create-button {
width: 71px !important;
min-width: 71px;
height: 32px;
background-color: var(--theme-create-button-background-color);
color: var(--theme-create-button-text-color);
text-overflow: ellipsis;
display: flex;
padding: 0;
}
.aca-upload-button {
width: 73px !important;
min-width: 73px;
height: 32px;
background-color: var(--theme-upload-button-background-color);
color: var(--theme-upload-button-text-color);
text-overflow: ellipsis;
display: flex;
padding: 0;
margin-left: 12px;
}
// .aca-process-button {
// width: 130px !important;
// min-width: 130px;
// height: 32px;
// background-color: var(--theme-upload-button-background-color);
// color: var(--theme-upload-button-text-color);
// text-overflow: ellipsis;
// display: flex;
// padding: 0;
// margin-left: 12px;
// }
}

View File

@@ -0,0 +1,109 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../testing/app-testing.module';
import { HeaderActionsComponent } from './header-actions.component';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatMenuHarness } from '@angular/material/menu/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { By } from '@angular/platform-browser';
describe('HeaderActionsComponent', () => {
let component: HeaderActionsComponent;
let fixture: ComponentFixture<HeaderActionsComponent>;
let loader: HarnessLoader;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule, NoopAnimationsModule, MatButtonModule, MatMenuModule],
declarations: [HeaderActionsComponent]
});
fixture = TestBed.createComponent(HeaderActionsComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('total number of buttons in header should be 2 if route is personal-files', async () => {
spyOn(component, 'isPersonalFilesRoute').and.returnValue(true);
const buttons = await loader.getAllHarnesses(MatButtonHarness);
const createButton = await loader.getAllHarnesses(MatButtonHarness.with({text: 'APP.HEADER.BUTTONS.CREATE'}));
const uploadButton = await loader.getAllHarnesses(MatButtonHarness.with({text: 'APP.HEADER.BUTTONS.UPLOAD'}));
expect(buttons.length).toBe(2);
expect(createButton.length).toBe(1);
expect(uploadButton.length).toBe(1);
});
it('total number of buttons in header should be 1 if route is libraries', async () => {
spyOn(component, 'isLibrariesRoute').and.returnValue(true);
const buttons = await loader.getAllHarnesses(MatButtonHarness);
const createButton = await loader.getAllHarnesses(MatButtonHarness.with({text: 'APP.HEADER.BUTTONS.CREATE'}));
expect(buttons.length).toBe(1);
expect(createButton.length).toBe(1);
});
it('should open and close the create menu', async () => {
spyOn(component, 'isPersonalFilesRoute').and.returnValue(true);
const createMenu = await loader.getHarness(MatMenuHarness.with({ triggerText: 'APP.HEADER.BUTTONS.CREATE' }));
expect(await createMenu.isOpen()).toBe(false);
await createMenu.open();
expect(await createMenu.isOpen()).toBe(true);
await createMenu.close();
expect(await createMenu.isOpen()).toBe(false);
});
it('should open and close the upload menu', async () => {
spyOn(component, 'isPersonalFilesRoute').and.returnValue(true);
const uploadMenu = await loader.getHarness(MatMenuHarness.with({ triggerText: 'APP.HEADER.BUTTONS.UPLOAD' }));
expect(await uploadMenu.isOpen()).toBe(false);
await uploadMenu.open();
expect(await uploadMenu.isOpen()).toBe(true);
await uploadMenu.close();
expect(await uploadMenu.isOpen()).toBe(false);
});
it('should load create menu on click of create button', async () => {
spyOn(component, 'isPersonalFilesRoute').and.returnValue(true);
const buttons = await loader.getHarness(MatButtonHarness.with({ selector: '.aca-create-button' }));
buttons.click();
const createMenu = fixture.debugElement.queryAll(By.css('.app-create-menu__root-menu app-create-menu__sub-menu'));
expect(createMenu).toBeTruthy();
});
it('should load upload menu on click of upload button', async () => {
spyOn(component, 'isPersonalFilesRoute').and.returnValue(true);
const buttons = await loader.getHarness(MatButtonHarness.with({ selector: '.aca-upload-button' }));
buttons.click();
const uploadMenu = fixture.debugElement.queryAll(By.css('.app-upload-menu__root-menu app-upload-menu__sub-menu'));
expect(uploadMenu).toBeTruthy();
});
});

View File

@@ -0,0 +1,96 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { ContentManagementService } from '../../services/content-management.service';
import { AppExtensionService, PageComponent } from '@alfresco/aca-shared';
import { SetCurrentFolderAction, AppStore } from '@alfresco/aca-shared/store';
@Component({
selector: 'aca-header-actions',
templateUrl: './header-actions.component.html',
styleUrls: ['./header-actions.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class HeaderActionsComponent extends PageComponent implements OnInit, OnDestroy {
constructor(private router: Router, store: Store<AppStore>, content: ContentManagementService, extensions: AppExtensionService) {
super(store, extensions, content);
}
ngOnInit() {
super.ngOnInit();
}
ngOnDestroy() {
this.store.dispatch(new SetCurrentFolderAction(null));
super.ngOnDestroy();
}
isPersonalFilesRoute(): boolean {
return this.router.url.includes('/personal-files');
}
isFavoriteLibrariesRoute(): boolean {
return this.router.url.includes('/favorite/libraries');
}
isLibrariesRoute(): boolean {
return this.router.url.includes('/libraries');
}
canShowCreateButton(): boolean {
if (this.isPersonalFilesRoute() || this.isFavoriteLibrariesRoute() || this.isLibrariesRoute()) {
return true;
} else {
return false;
}
}
canShowUploadButton(): boolean {
if (this.isPersonalFilesRoute()) {
return true;
} else {
return false;
}
}
canShowSearchSeparator(): boolean {
if (this.isPersonalFilesRoute() || this.isFavoriteLibrariesRoute() || this.isLibrariesRoute()) {
return true;
} else {
return false;
}
}
isTasksRoute(): boolean {
return this.router.url.includes('/tasks');
}
isProcessesRoute(): boolean {
return this.router.url.includes('/processes');
}
}

View File

@@ -0,0 +1,55 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@alfresco/adf-core';
import { ContentModule } from '@alfresco/adf-content-services';
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 { HeaderActionsComponent } from './header-actions.component';
import { MainActionModule } from '../main-action/main-action.module';
@NgModule({
imports: [
CommonModule,
CoreModule.forChild(),
ContentModule.forChild(),
DirectivesModule,
AppCommonModule,
AppToolbarModule,
ContextMenuModule,
AppLayoutModule,
AppSearchInputModule,
MainActionModule
],
declarations: [HeaderActionsComponent],
exports: [HeaderActionsComponent]
})
export class AppHeaderActionsModule {}

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.LIBRARIES.MENU.MY_LIBRARIES.TITLE"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.RECENT.TITLE"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -6,6 +6,7 @@ $top-margin: 12px;
font-size: 16px;
padding-left: 15px;
box-sizing: border-box;
margin-bottom: 12px !important;
.mat-form-field {
font-size: 16px;

View File

@@ -0,0 +1,66 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { CoreModule } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { SearchInputService } from './search-input.service';
import { Router } from '@angular/router';
describe('SearchInputService', () => {
let service: SearchInputService;
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), CoreModule.forRoot()]
});
service = TestBed.inject(SearchInputService);
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.savedRoute = '';
service.exitSearch();
expect(routerNavigate).not.toHaveBeenCalledWith([service.savedRoute]);
});
it('should navigate to saved route when exitSearch function is called', () => {
const routerNavigate = spyOn(router, 'navigate');
service.savedRoute = '/personal-files';
service.exitSearch();
expect(routerNavigate).toHaveBeenCalledWith([service.savedRoute]);
});
it('should navigate to Search when navigateToSearch function is called', () => {
const routerNavigate = spyOn(router, 'navigate');
service.navigateToSearch();
expect(routerNavigate).toHaveBeenCalledWith(['/search']);
});
});

View File

@@ -0,0 +1,55 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class SearchInputService {
savedRoute = '';
constructor(private router: Router) {}
isSearchRoute(): boolean {
return this.router.url.includes('/search');
}
saveRoute(route: string) {
this.savedRoute = route;
}
exitSearch() {
if (this.savedRoute.length > 0) {
this.router.navigate([this.savedRoute]);
}
}
navigateToSearch() {
this.saveRoute(this.router.url);
this.router.navigate(['/search']);
}
}

View File

@@ -1,9 +1,10 @@
<div
<div *ngIf="searchInputService.isSearchRoute()"
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,9 +21,20 @@
<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()" (keypressk)="exitSearch()">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</div>
<div *ngIf="!searchInputService.isSearchRoute()"
class="app-search-container">
<button mat-icon-button class="app-search-button" (click)="navigateToSearch()" [title]="'SEARCH.BUTTON.TOOLTIP' | translate">
<mat-icon [attr.aria-label]="'SEARCH.BUTTON.ARIA-LABEL' | translate">search</mat-icon>
</button>
</div>
<mat-menu #searchOptionsMenu="matMenu" [overlapTrigger]="true" class="app-search-options-menu">
<div (keydown.tab)="$event.stopPropagation()" (keydown.shift.tab)="$event.stopPropagation()">
<div cdkTrapFocus>

View File

@@ -1,13 +1,33 @@
$search-width: 594px;
$search-height: 40px;
$search-height: 32px;
$search-background: #f5f6f5;
$search-border-radius: 4px;
$top-margin: 12px;
.app-search-container {
color: var(--theme-foreground-text-color);
max-width: 100%;
margin: 0 !important;
.app-search-button {
width: 32px !important;
height: 32px !important;
margin-left: 0 !important;
padding-left: 0 !important;
margin-top: -4px !important;
}
.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);
@@ -54,6 +74,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-container {

View File

@@ -32,12 +32,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 { SearchInputService } from '../search-input.service';
describe('SearchInputComponent', () => {
let fixture: ComponentFixture<SearchInputComponent>;
let component: SearchInputComponent;
let actions$: Actions;
let appHookService: AppHookService;
let searchInputService: SearchInputService;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -50,12 +52,21 @@ describe('SearchInputComponent', () => {
actions$ = TestBed.inject(Actions);
fixture = TestBed.createComponent(SearchInputComponent);
appHookService = TestBed.inject(AppHookService);
searchInputService = TestBed.inject(SearchInputService);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should change flag on library400Error event', () => {
afterEach(() => {
fixture.destroy();
});
it('should change flag on library400Error event', async () => {
spyOn(searchInputService, 'isSearchRoute').and.returnValue(true);
fixture.detectChanges();
await fixture.whenStable();
expect(component.has400LibraryError).toBe(false);
appHookService.library400Error.next();
expect(component.has400LibraryError).toBe(true);
@@ -65,9 +76,14 @@ 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 () => {
spyOn(searchInputService, 'isSearchRoute').and.returnValue(true);
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);
@@ -193,4 +209,44 @@ describe('SearchInputComponent', () => {
expect(component.isContentChecked()).toBe(true);
});
});
describe('navigateToSearch()', () => {
it('should navigate to search on click of search icon', async () => {
spyOn(searchInputService, 'isSearchRoute').and.returnValue(false);
spyOn(component, 'navigateToSearch').and.callThrough();
spyOn(searchInputService, 'navigateToSearch').and.callThrough();
fixture.detectChanges();
await fixture.whenStable();
const searchIcon = fixture.debugElement.nativeElement.querySelector('.app-search-button');
searchIcon.click();
fixture.detectChanges();
await fixture.whenStable();
expect(component.navigateToSearch).toHaveBeenCalled();
expect(searchInputService.navigateToSearch).toHaveBeenCalledWith();
});
});
describe('exitSearch()', () => {
it('should exit search on click of close icon', async () => {
spyOn(searchInputService, 'isSearchRoute').and.returnValue(true);
spyOn(component, 'exitSearch').and.callThrough();
spyOn(searchInputService, 'exitSearch').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.exitSearch).toHaveBeenCalledWith();
});
});
});

View File

@@ -34,6 +34,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 { SearchInputService } from '../search-input.service';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
@Component({
@@ -85,12 +86,14 @@ export class SearchInputComponent implements OnInit, OnDestroy {
private config: AppConfigService,
private router: Router,
private store: Store<AppStore>,
private appHookService: AppHookService
private appHookService: AppHookService,
public searchInputService: SearchInputService
) {
this.searchOnChange = this.config.get<boolean>('search.aca:triggeredOnChange', true);
}
ngOnInit() {
if (this.searchInputService.isSearchRoute()) {
this.showInputValue();
this.router.events
@@ -106,6 +109,15 @@ export class SearchInputComponent implements OnInit, OnDestroy {
this.has400LibraryError = true;
});
}
}
navigateToSearch() {
this.searchInputService.navigateToSearch();
}
exitSearch() {
this.searchInputService.exitSearch();
}
showInputValue() {
this.has400LibraryError = false;
@@ -140,7 +152,6 @@ export class SearchInputComponent implements OnInit, OnDestroy {
} else {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.EMPTY_SEARCH'));
}
this.trigger.closeMenu();
}
onSearchChange(searchTerm: string) {

View File

@@ -39,6 +39,8 @@ 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';
import { AppHeaderActionsModule } from '../header-actions/header-actions.module';
@NgModule({
imports: [
@@ -52,7 +54,9 @@ import { DocumentListCustomComponentsModule } from '../dl-custom-components/docu
AppLayoutModule,
ContextMenuModule,
LockedByModule,
DocumentListCustomComponentsModule
DocumentListCustomComponentsModule,
AppSearchInputModule,
AppHeaderActionsModule
],
declarations: [SearchResultsComponent, SearchLibrariesResultsComponent, SearchResultsRowComponent, SearchActionMenuComponent],
exports: [SearchResultsComponent, SearchLibrariesResultsComponent, SearchResultsRowComponent, SearchActionMenuComponent]

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE"> </adf-breadcrumb>
<adf-toolbar class="adf-toolbar--inline">
<aca-search-input></aca-search-input>
<adf-toolbar class="adf-toolbar--inline adf-toolbar-search-results">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>
</ng-container>

View File

@@ -3,6 +3,13 @@
$adf-chip-background: #efefef;
$contrast-gray: #646569;
.adf-toolbar-search-results {
position: fixed;
right: 24px;
height: 32px;
margin-bottom: 32px !important;
}
.adf-search-results {
@include flex-row;

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.SHARED.TITLE"></adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -1,7 +1,7 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-breadcrumb root="APP.BROWSE.TRASHCAN.TITLE"> </adf-breadcrumb>
<aca-header-actions></aca-header-actions>
<adf-toolbar class="adf-toolbar--inline">
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>

View File

@@ -33,6 +33,8 @@ 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 { AppHeaderActionsModule } from '../header-actions/header-actions.module';
@NgModule({
imports: [
@@ -43,7 +45,9 @@ import { AppLayoutModule } from '../layout/layout.module';
AppCommonModule,
AppToolbarModule,
ContextMenuModule,
AppLayoutModule
AppLayoutModule,
AppSearchInputModule,
AppHeaderActionsModule
],
declarations: [TrashcanComponent],
exports: [TrashcanComponent]

View File

@@ -51,6 +51,9 @@ $selected-text-color: #212121;
$selected-background-color: rgba(31, 116, 219, 0.24);
$action-button-text-color: rgba(33, 35, 40, 0.7);
$tooltip-background-color: #ffffff;
$create-button-text-color: #212121;
$upload-button-background-color: #2A7DE1;
$page-layout-header-background-color: #ffffff;
$defaults: (
--theme-primary-color: mat.get-color-from-palette($primary),
@@ -95,6 +98,11 @@ $defaults: (
--theme-action-button-text-color: $action-button-text-color,
--theme-header-border-color: $grey-background,
--theme-tooltip-background-color: $tooltip-background-color,
--theme-page-layout-header-background-color: $page-layout-header-background-color,
--theme-create-button-background-color: $grey-text-background,
--theme-create-button-text-color: $create-button-text-color,
--theme-upload-button-background-color: $upload-button-background-color,
--theme-upload-button-text-color: $pagination-background-color,
);
// propagates SCSS variables into the CSS variables scope

View File

@@ -25,7 +25,7 @@
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services';
import { ShowHeaderMode } from '@alfresco/adf-core';
import { ContentActionRef, DocumentListPresetRef, SelectionState } from '@alfresco/adf-extensions';
import { ContentActionRef, ContentActionType, DocumentListPresetRef, SelectionState } from '@alfresco/adf-extensions';
import { OnDestroy, OnInit, OnChanges, ViewChild, SimpleChanges, Directive } from '@angular/core';
import { Store } from '@ngrx/store';
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging } from '@alfresco/js-api';
@@ -68,12 +68,32 @@ export abstract class PageComponent implements OnInit, OnDestroy, OnChanges {
nodeResult: NodePaging;
showHeader = ShowHeaderMode.Data;
filterSorting = 'name-asc';
createActions: Array<ContentActionRef> = [];
uploadActions: Array<ContentActionRef> = [];
mainAction$: Observable<ContentActionRef>;
actionTypes = ContentActionType;
protected subscriptions: Subscription[] = [];
protected constructor(protected store: Store<AppStore>, protected extensions: AppExtensionService, protected content: DocumentBasePageService) {}
ngOnInit() {
this.mainAction$ = this.extensions.getMainAction().pipe(takeUntil(this.onDestroy$));
this.extensions
.getCreateActions()
.pipe(takeUntil(this.onDestroy$))
.subscribe((actions) => {
this.createActions = actions;
});
this.extensions
.getUploadActions()
.pipe(takeUntil(this.onDestroy$))
.subscribe((actions) => {
this.uploadActions = actions;
});
this.sharedPreviewUrl$ = this.store.select(getSharedUrl);
this.infoDrawerOpened$ = this.store.select(isInfoDrawerOpened).pipe(map((infoDrawerState) => !this.isOutletPreviewUrl() && infoDrawerState));
@@ -124,6 +144,10 @@ export abstract class PageComponent implements OnInit, OnDestroy, OnChanges {
this.store.dispatch(new SetSelectedNodesAction([]));
}
runAction(action: string): void {
this.extensions.runActionById(action);
}
showPreview(node: MinimalNodeEntity, extras?: ViewNodeExtras) {
if (node && node.entry) {
let id: string;

View File

@@ -7,8 +7,8 @@
display: flex;
align-items: center;
flex: 0 0 65px;
flex-basis: 48px;
background: var(--theme-background-color);
flex-basis: 96px;
background: var(--theme-page-layout-header-background-color);
border-bottom: 1px solid var(--theme-border-color, rgba(0, 0, 0, 0.07));
padding: 0 24px;
}

View File

@@ -79,6 +79,7 @@ export class AppExtensionService implements RuleContext {
private _contextMenuActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _openWithActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _createActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _uploadActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private _mainActions = new BehaviorSubject<ContentActionRef>(null);
private _sidebarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
@@ -158,6 +159,7 @@ export class AppExtensionService implements RuleContext {
this._contextMenuActions.next(this.loader.getContentActions(config, 'features.contextMenu'));
this._openWithActions.next(this.loader.getContentActions(config, 'features.viewer.openWith'));
this._createActions.next(this.loader.getElements<ContentActionRef>(config, 'features.create'));
this._uploadActions.next(this.loader.getElements<ContentActionRef>(config, 'features.upload'));
this._mainActions.next(this.loader.getFeatures(config).mainAction);
this.navbar = this.loadNavBar(config);
@@ -366,6 +368,18 @@ export class AppExtensionService implements RuleContext {
);
}
getUploadActions(): Observable<Array<ContentActionRef>> {
return this._uploadActions.pipe(
map((uploadActions) =>
uploadActions
.filter((action) => this.filterVisible(action))
.map((action) => this.copyAction(action))
.map((action) => this.buildMenu(action))
.map((action) => this.setActionDisabledFromRule(action))
)
);
}
getMainAction(): Observable<ContentActionRef> {
return this._mainActions.pipe(
filter((mainAction) => mainAction && this.filterVisible(mainAction)),