diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index dcc7d039a..91b99041f 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -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", diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index 98675b87b..f469fe11c 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -103,6 +103,12 @@ "CREATE_LIBRARY": "Create a new File Library" } }, + "HEADER": { + "BUTTONS": { + "CREATE": "Create", + "UPLOAD": "Upload" + } + }, "BROWSE": { "FILE": { "TITLE": "Files", diff --git a/projects/aca-content/src/lib/aca-content.module.ts b/projects/aca-content/src/lib/aca-content.module.ts index 8623e7729..90d368c4f 100644 --- a/projects/aca-content/src/lib/aca-content.module.ts +++ b/projects/aca-content/src/lib/aca-content.module.ts @@ -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, diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html index eda35a440..96becc211 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/favorites/favorites.component.html b/projects/aca-content/src/lib/components/favorites/favorites.component.html index 4770e1d67..d7128b9b1 100644 --- a/projects/aca-content/src/lib/components/favorites/favorites.component.html +++ b/projects/aca-content/src/lib/components/favorites/favorites.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/files/files.component.html b/projects/aca-content/src/lib/components/files/files.component.html index 2c1bedc83..1084b433b 100644 --- a/projects/aca-content/src/lib/components/files/files.component.html +++ b/projects/aca-content/src/lib/components/files/files.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/header-actions/header-actions.component.html b/projects/aca-content/src/lib/components/header-actions/header-actions.component.html new file mode 100644 index 000000000..a5bd756ab --- /dev/null +++ b/projects/aca-content/src/lib/components/header-actions/header-actions.component.html @@ -0,0 +1,33 @@ +
+
+ + + +
+ +
+
+ + + +
+ +
+
+ + + + + + +
+
\ No newline at end of file diff --git a/projects/aca-content/src/lib/components/header-actions/header-actions.component.scss b/projects/aca-content/src/lib/components/header-actions/header-actions.component.scss new file mode 100644 index 000000000..46124d3ac --- /dev/null +++ b/projects/aca-content/src/lib/components/header-actions/header-actions.component.scss @@ -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; + // } + } \ No newline at end of file diff --git a/projects/aca-content/src/lib/components/header-actions/header-actions.component.spec.ts b/projects/aca-content/src/lib/components/header-actions/header-actions.component.spec.ts new file mode 100644 index 000000000..6b11a89a6 --- /dev/null +++ b/projects/aca-content/src/lib/components/header-actions/header-actions.component.spec.ts @@ -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; + 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(); + }); +}); diff --git a/projects/aca-content/src/lib/components/header-actions/header-actions.component.ts b/projects/aca-content/src/lib/components/header-actions/header-actions.component.ts new file mode 100644 index 000000000..1aebc00ab --- /dev/null +++ b/projects/aca-content/src/lib/components/header-actions/header-actions.component.ts @@ -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 . + */ + +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, 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'); + } +} diff --git a/projects/aca-content/src/lib/components/header-actions/header-actions.module.ts b/projects/aca-content/src/lib/components/header-actions/header-actions.module.ts new file mode 100644 index 000000000..8205ec9c1 --- /dev/null +++ b/projects/aca-content/src/lib/components/header-actions/header-actions.module.ts @@ -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 . + */ + +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 {} diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.html b/projects/aca-content/src/lib/components/libraries/libraries.component.html index 0f7c0aa4f..d917f5123 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.html +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/recent-files/recent-files.component.html b/projects/aca-content/src/lib/components/recent-files/recent-files.component.html index 4c95af8f2..9004b2d2f 100644 --- a/projects/aca-content/src/lib/components/recent-files/recent-files.component.html +++ b/projects/aca-content/src/lib/components/recent-files/recent-files.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.scss b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.scss index 1182703f9..578af6fc6 100644 --- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.scss +++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.scss @@ -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; diff --git a/projects/aca-content/src/lib/components/search/search-input.service.spec.ts b/projects/aca-content/src/lib/components/search/search-input.service.spec.ts new file mode 100644 index 000000000..b1f27408b --- /dev/null +++ b/projects/aca-content/src/lib/components/search/search-input.service.spec.ts @@ -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 . + */ + +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']); + }); +}); diff --git a/projects/aca-content/src/lib/components/search/search-input.service.ts b/projects/aca-content/src/lib/components/search/search-input.service.ts new file mode 100644 index 000000000..0a8430605 --- /dev/null +++ b/projects/aca-content/src/lib/components/search/search-input.service.ts @@ -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 . + */ + +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']); + } +} diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html index b9a0ab48f..33c173fbc 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html @@ -1,13 +1,14 @@ -
+ - + arrow_drop_down
- + + + + + +
+
diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.scss b/projects/aca-content/src/lib/components/search/search-input/search-input.component.scss index 0c8151778..126a83665 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.scss +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.scss @@ -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 { diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.spec.ts b/projects/aca-content/src/lib/components/search/search-input/search-input.component.spec.ts index 8555e2087..a1eab7b34 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.spec.ts +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.spec.ts @@ -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; 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(); + }); + }); }); diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts b/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts index e63500803..1cd5b4792 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts @@ -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,26 +86,37 @@ export class SearchInputComponent implements OnInit, OnDestroy { private config: AppConfigService, private router: Router, private store: Store, - private appHookService: AppHookService + private appHookService: AppHookService, + public searchInputService: SearchInputService ) { this.searchOnChange = this.config.get('search.aca:triggeredOnChange', true); } ngOnInit() { - this.showInputValue(); + if (this.searchInputService.isSearchRoute()) { + this.showInputValue(); - this.router.events - .pipe(takeUntil(this.onDestroy$)) - .pipe(filter((e) => e instanceof RouterEvent)) - .subscribe((event) => { - if (event instanceof NavigationEnd) { - this.showInputValue(); - } + this.router.events + .pipe(takeUntil(this.onDestroy$)) + .pipe(filter((e) => e instanceof RouterEvent)) + .subscribe((event) => { + if (event instanceof NavigationEnd) { + this.showInputValue(); + } + }); + + this.appHookService.library400Error.pipe(takeUntil(this.onDestroy$)).subscribe(() => { + this.has400LibraryError = true; }); + } + } - this.appHookService.library400Error.pipe(takeUntil(this.onDestroy$)).subscribe(() => { - this.has400LibraryError = true; - }); + navigateToSearch() { + this.searchInputService.navigateToSearch(); + } + + exitSearch() { + this.searchInputService.exitSearch(); } showInputValue() { @@ -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) { diff --git a/projects/aca-content/src/lib/components/search/search-results.module.ts b/projects/aca-content/src/lib/components/search/search-results.module.ts index f5f80a7c7..93c41d1b9 100644 --- a/projects/aca-content/src/lib/components/search/search-results.module.ts +++ b/projects/aca-content/src/lib/components/search/search-results.module.ts @@ -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] diff --git a/projects/aca-content/src/lib/components/search/search-results/search-results.component.html b/projects/aca-content/src/lib/components/search/search-results/search-results.component.html index 0c652273e..02f3e8b61 100644 --- a/projects/aca-content/src/lib/components/search/search-results/search-results.component.html +++ b/projects/aca-content/src/lib/components/search/search-results/search-results.component.html @@ -1,7 +1,7 @@ - - + + diff --git a/projects/aca-content/src/lib/components/search/search-results/search-results.component.scss b/projects/aca-content/src/lib/components/search/search-results/search-results.component.scss index 9a851b73a..5e11fb988 100644 --- a/projects/aca-content/src/lib/components/search/search-results/search-results.component.scss +++ b/projects/aca-content/src/lib/components/search/search-results/search-results.component.scss @@ -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; diff --git a/projects/aca-content/src/lib/components/shared-files/shared-files.component.html b/projects/aca-content/src/lib/components/shared-files/shared-files.component.html index e5f2dbec2..05607dbc7 100644 --- a/projects/aca-content/src/lib/components/shared-files/shared-files.component.html +++ b/projects/aca-content/src/lib/components/shared-files/shared-files.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/trashcan/trashcan.component.html b/projects/aca-content/src/lib/components/trashcan/trashcan.component.html index a253fc328..d09c65102 100644 --- a/projects/aca-content/src/lib/components/trashcan/trashcan.component.html +++ b/projects/aca-content/src/lib/components/trashcan/trashcan.component.html @@ -1,7 +1,7 @@ - + diff --git a/projects/aca-content/src/lib/components/trashcan/trashcan.module.ts b/projects/aca-content/src/lib/components/trashcan/trashcan.module.ts index 120f04a60..97e6aecb9 100644 --- a/projects/aca-content/src/lib/components/trashcan/trashcan.module.ts +++ b/projects/aca-content/src/lib/components/trashcan/trashcan.module.ts @@ -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] diff --git a/projects/aca-content/src/lib/ui/variables/variables.scss b/projects/aca-content/src/lib/ui/variables/variables.scss index 6773d6c15..6507286f8 100644 --- a/projects/aca-content/src/lib/ui/variables/variables.scss +++ b/projects/aca-content/src/lib/ui/variables/variables.scss @@ -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 diff --git a/projects/aca-shared/src/lib/components/document-base-page/document-base-page.component.ts b/projects/aca-shared/src/lib/components/document-base-page/document-base-page.component.ts index becd13924..cdaf73f5e 100644 --- a/projects/aca-shared/src/lib/components/document-base-page/document-base-page.component.ts +++ b/projects/aca-shared/src/lib/components/document-base-page/document-base-page.component.ts @@ -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 = []; + uploadActions: Array = []; + mainAction$: Observable; + actionTypes = ContentActionType; protected subscriptions: Subscription[] = []; protected constructor(protected store: Store, 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; diff --git a/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss b/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss index 2d0dddb59..2220d999f 100644 --- a/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss +++ b/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss @@ -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; } diff --git a/projects/aca-shared/src/lib/services/app.extension.service.ts b/projects/aca-shared/src/lib/services/app.extension.service.ts index 9ac402f26..8a548ea5a 100644 --- a/projects/aca-shared/src/lib/services/app.extension.service.ts +++ b/projects/aca-shared/src/lib/services/app.extension.service.ts @@ -79,6 +79,7 @@ export class AppExtensionService implements RuleContext { private _contextMenuActions = new BehaviorSubject>([]); private _openWithActions = new BehaviorSubject>([]); private _createActions = new BehaviorSubject>([]); + private _uploadActions = new BehaviorSubject>([]); private _mainActions = new BehaviorSubject(null); private _sidebarActions = new BehaviorSubject>([]); @@ -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(config, 'features.create')); + this._uploadActions.next(this.loader.getElements(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> { + 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 { return this._mainActions.pipe( filter((mainAction) => mainAction && this.filterVisible(mainAction)),