diff --git a/src/app/components/location-link/location-link.component.ts b/src/app/components/location-link/location-link.component.ts index 0277f76ce..b72c25fae 100644 --- a/src/app/components/location-link/location-link.component.ts +++ b/src/app/components/location-link/location-link.component.ts @@ -25,12 +25,12 @@ import { Component, Input, ChangeDetectionStrategy, OnInit, ViewEncapsulation } from '@angular/core'; import { AlfrescoApiService, DataColumn, DataRow, DataTableAdapter } from '@alfresco/adf-core'; -import { PathInfoEntity } from 'alfresco-js-api'; +import { PathInfoEntity, MinimalNodeEntity } from 'alfresco-js-api'; import { Observable } from 'rxjs/Rx'; import { Store } from '@ngrx/store'; import { AppStore } from '../../store/states/app.state'; -import { NavigateToLocationAction } from '../../store/actions'; +import { NavigateToParentFolder } from '../../store/actions'; @Component({ selector: 'app-location-link', @@ -64,9 +64,8 @@ export class LocationLinkComponent implements OnInit { goToLocation() { if (this.context) { - const { node } = this.context.row; - - this.store.dispatch(new NavigateToLocationAction(node.entry)); + const node: MinimalNodeEntity = this.context.row.node; + this.store.dispatch(new NavigateToParentFolder(node)); } } diff --git a/src/app/components/search-input/search-input.component.spec.ts b/src/app/components/search-input/search-input.component.spec.ts index 62d7101c9..82de63b77 100644 --- a/src/app/components/search-input/search-input.component.spec.ts +++ b/src/app/components/search-input/search-input.component.spec.ts @@ -24,23 +24,23 @@ */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TestBed, async } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { SearchInputComponent } from './search-input.component'; -import { TranslateModule } from '@ngx-translate/core'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { Actions, ofType } from '@ngrx/effects'; +import { ViewNodeAction, VIEW_NODE, NAVIGATE_FOLDER, NavigateToFolder } from '../../store/actions'; +import { map } from 'rxjs/operators'; describe('SearchInputComponent', () => { - let fixture; - let component; - let router: Router; + let fixture: ComponentFixture; + let component: SearchInputComponent; + let actions$: Actions; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule, - TranslateModule.forRoot() + AppTestingModule ], declarations: [ SearchInputComponent @@ -49,32 +49,40 @@ describe('SearchInputComponent', () => { }) .compileComponents() .then(() => { + actions$ = TestBed.get(Actions); fixture = TestBed.createComponent(SearchInputComponent); component = fixture.componentInstance; - router = TestBed.get(Router); - fixture.detectChanges(); }); })); describe('onItemClicked()', () => { - it('opens preview if node is file', () => { - spyOn(router, 'navigate').and.stub(); + it('opens preview if node is file', fakeAsync(done => { + actions$.pipe( + ofType(VIEW_NODE), + map(action => { + expect(action.payload.id).toBe('node-id'); + done(); + }) + ); + const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; component.onItemClicked(node); + tick(); + })); - expect(router.navigate['calls'].argsFor(0)[0]) - .toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); - }); - - it('navigates if node is folder', () => { - const node = { entry: { isFolder: true } }; - spyOn(router, 'navigate'); - + it('navigates if node is folder', fakeAsync(done => { + actions$.pipe( + ofType(NAVIGATE_FOLDER), + map(action => { + expect(action.payload.entry.id).toBe('folder-id'); + done(); + }) + ); + const node = { entry: { id: 'folder-id', isFolder: true } }; component.onItemClicked(node); - - expect(router.navigate).toHaveBeenCalled(); - }); + tick(); + })); }); }); diff --git a/src/app/components/search-input/search-input.component.ts b/src/app/components/search-input/search-input.component.ts index 71b90270d..860d87d5e 100644 --- a/src/app/components/search-input/search-input.component.ts +++ b/src/app/components/search-input/search-input.component.ts @@ -30,6 +30,9 @@ import { } from '@angular/router'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { SearchControlComponent } from '@alfresco/adf-content-services'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { SearchByTermAction, ViewNodeAction, NavigateToFolder } from '../../store/actions'; @Component({ selector: 'aca-search-input', @@ -46,7 +49,7 @@ export class SearchInputComponent implements OnInit { @ViewChild('searchControl') searchControl: SearchControlComponent; - constructor(private router: Router) { + constructor(private router: Router, private store: Store) { this.router.events.filter(e => e instanceof RouterEvent).subscribe(event => { if (event instanceof NavigationEnd) { this.showInputValue(); @@ -84,10 +87,17 @@ export class SearchInputComponent implements OnInit { onItemClicked(node: MinimalNodeEntity) { if (node && node.entry) { - if (node.entry.isFile) { - this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); - } else if (node.entry.isFolder) { - this.router.navigate([ '/personal-files', node.entry.id ]); + const { id, nodeId, name, isFile, isFolder, parentId } = node.entry; + if (isFile) { + this.store.dispatch(new ViewNodeAction({ + parentId, + id: nodeId || id, + name, + isFile, + isFolder + })); + } else if (isFolder) { + this.store.dispatch(new NavigateToFolder(node)); } } } @@ -98,13 +108,11 @@ export class SearchInputComponent implements OnInit { * @param event Parameters relating to the search */ onSearchSubmit(event: KeyboardEvent) { - const value = (event.target as HTMLInputElement).value; - this.router.navigate(['/search', { - q: value - }]); + const searchTerm = (event.target as HTMLInputElement).value; + this.store.dispatch(new SearchByTermAction(searchTerm)); } - onSearchChange(event: string) { + onSearchChange(searchTerm: string) { if (this.onSearchResults) { if (this.hasOneChange) { @@ -119,8 +127,8 @@ export class SearchInputComponent implements OnInit { } this.navigationTimer = setTimeout(() => { - if (event) { - this.router.navigate(['/search', {q: event}]); + if (searchTerm) { + this.store.dispatch(new SearchByTermAction(searchTerm)); } this.hasOneChange = false; }, 1000); diff --git a/src/app/components/search/search.component.ts b/src/app/components/search/search.component.ts index ce672a6a3..8773b5514 100644 --- a/src/app/components/search/search.component.ts +++ b/src/app/components/search/search.component.ts @@ -31,7 +31,7 @@ import { UserPreferencesService } from '@alfresco/adf-core'; import { PageComponent } from '../page.component'; import { Store } from '@ngrx/store'; import { AppStore } from '../../store/states/app.state'; -import { NavigateToLocationAction } from '../../store/actions'; +import { NavigateToFolder } from '../../store/actions'; @Component({ selector: 'app-search', @@ -136,7 +136,7 @@ export class SearchComponent extends PageComponent implements OnInit { onNodeDoubleClick(node: MinimalNodeEntity) { if (node && node.entry) { if (node.entry.isFolder) { - this.store.dispatch(new NavigateToLocationAction(node.entry)); + this.store.dispatch(new NavigateToFolder(node)); return; } diff --git a/src/app/store/actions.ts b/src/app/store/actions.ts index 11e24413a..d3cec5741 100644 --- a/src/app/store/actions.ts +++ b/src/app/store/actions.ts @@ -28,3 +28,4 @@ export * from './actions/node.actions'; export * from './actions/snackbar.actions'; export * from './actions/router.actions'; export * from './actions/viewer.actions'; +export * from './actions/search.actions'; diff --git a/src/app/store/actions/router.actions.ts b/src/app/store/actions/router.actions.ts index 457b28ef4..2e3faa760 100644 --- a/src/app/store/actions/router.actions.ts +++ b/src/app/store/actions/router.actions.ts @@ -24,16 +24,24 @@ */ import { Action } from '@ngrx/store'; +import { MinimalNodeEntity } from 'alfresco-js-api'; export const NAVIGATE_ROUTE = 'NAVIGATE_ROUTE'; -export const NAVIGATE_LOCATION = 'NAVIGATE_LOCATION'; +export const NAVIGATE_FOLDER = 'NAVIGATE_FOLDER'; +export const NAVIGATE_PARENT_FOLDER = 'NAVIGATE_PARENT_FOLDER'; export class NavigateRouteAction implements Action { readonly type = NAVIGATE_ROUTE; constructor(public payload: any[]) {} } -export class NavigateToLocationAction implements Action { - readonly type = NAVIGATE_LOCATION; - constructor(public payload: any) {} +export class NavigateToFolder implements Action { + readonly type = NAVIGATE_FOLDER; + constructor(public payload: MinimalNodeEntity) {} +} + + +export class NavigateToParentFolder implements Action { + readonly type = NAVIGATE_PARENT_FOLDER; + constructor(public payload: MinimalNodeEntity) {} } diff --git a/src/app/store/actions/search.actions.ts b/src/app/store/actions/search.actions.ts new file mode 100644 index 000000000..5d3632bb6 --- /dev/null +++ b/src/app/store/actions/search.actions.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; + +export const SEARCH_BY_TERM = 'SEARCH_BY_TERM'; + +export class SearchByTermAction implements Action { + readonly type = SEARCH_BY_TERM; + constructor(public payload: string) {} +} diff --git a/src/app/store/app-store.module.ts b/src/app/store/app-store.module.ts index 7000b3537..6c547ee28 100644 --- a/src/app/store/app-store.module.ts +++ b/src/app/store/app-store.module.ts @@ -36,7 +36,8 @@ import { NodeEffects, RouterEffects, DownloadEffects, - ViewerEffects + ViewerEffects, + SearchEffects } from './effects'; @NgModule({ @@ -51,7 +52,8 @@ import { NodeEffects, RouterEffects, DownloadEffects, - ViewerEffects + ViewerEffects, + SearchEffects ]), !environment.production ? StoreDevtoolsModule.instrument({ maxAge: 25 }) diff --git a/src/app/store/effects.ts b/src/app/store/effects.ts index 463ac1680..c4f6826e8 100644 --- a/src/app/store/effects.ts +++ b/src/app/store/effects.ts @@ -28,3 +28,4 @@ export * from './effects/node.effects'; export * from './effects/router.effects'; export * from './effects/snackbar.effects'; export * from './effects/viewer.effects'; +export * from './effects/search.effects'; diff --git a/src/app/store/effects/router.effects.ts b/src/app/store/effects/router.effects.ts index 566d65db5..e0f66c4b3 100644 --- a/src/app/store/effects/router.effects.ts +++ b/src/app/store/effects/router.effects.ts @@ -30,10 +30,11 @@ import { MinimalNodeEntryEntity, PathInfoEntity } from 'alfresco-js-api'; import { map } from 'rxjs/operators'; import { NavigateRouteAction, - NavigateToLocationAction, - NAVIGATE_LOCATION, + NavigateToParentFolder, + NAVIGATE_PARENT_FOLDER, NAVIGATE_ROUTE } from '../actions'; +import { NavigateToFolder, NAVIGATE_FOLDER } from '../actions/router.actions'; @Injectable() export class RouterEffects { @@ -48,16 +49,49 @@ export class RouterEffects { ); @Effect({ dispatch: false }) - navigateLocation$ = this.actions$.pipe( - ofType(NAVIGATE_LOCATION), + navigateToFolder$ = this.actions$.pipe( + ofType(NAVIGATE_FOLDER), map(action => { - if (action.payload) { - this.navigateToLocation(action.payload); + if (action.payload && action.payload.entry) { + this.navigateToFolder(action.payload.entry); } }) ); - private navigateToLocation(node: MinimalNodeEntryEntity) { + @Effect({ dispatch: false }) + navigateToParentFolder$ = this.actions$.pipe( + ofType(NAVIGATE_PARENT_FOLDER), + map(action => { + if (action.payload && action.payload.entry) { + this.navigateToParentFolder(action.payload.entry); + } + }) + ); + + private navigateToFolder(node: MinimalNodeEntryEntity) { + let link = null; + const { path, id } = node; + + if (path && path.name && path.elements) { + const isLibraryPath = this.isLibraryContent(path); + + const parent = path.elements[path.elements.length - 1]; + const area = isLibraryPath ? '/libraries' : '/personal-files'; + + if (!isLibraryPath) { + link = [area, id]; + } else { + // parent.id could be 'Site' folder or child as 'documentLibrary' + link = [area, parent.name === 'Sites' ? {} : id]; + } + } + + setTimeout(() => { + this.router.navigate(link); + }, 10); + } + + private navigateToParentFolder(node: MinimalNodeEntryEntity) { let link = null; const { path } = node; diff --git a/src/app/store/effects/search.effects.ts b/src/app/store/effects/search.effects.ts new file mode 100644 index 000000000..0d5dd150f --- /dev/null +++ b/src/app/store/effects/search.effects.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 { Effect, Actions, ofType } from '@ngrx/effects'; +import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { SEARCH_BY_TERM, SearchByTermAction } from '../actions/search.actions'; +import { Router } from '@angular/router'; + +@Injectable() +export class SearchEffects { + constructor(private actions$: Actions, private router: Router) {} + + @Effect({ dispatch: false }) + searchByTerm$ = this.actions$.pipe( + ofType(SEARCH_BY_TERM), + map(action => { + this.router.navigateByUrl('/search;q=' + action.payload); + }) + ); +} diff --git a/src/app/testing/app-testing.module.ts b/src/app/testing/app-testing.module.ts index e061a03de..4768a150e 100644 --- a/src/app/testing/app-testing.module.ts +++ b/src/app/testing/app-testing.module.ts @@ -34,13 +34,15 @@ import { StoreModule } from '@ngrx/store'; import { appReducer } from '../store/reducers/app.reducer'; import { INITIAL_STATE } from '../store/states/app.state'; import { RouterTestingModule } from '@angular/router/testing'; +import { EffectsModule } from '@ngrx/effects'; @NgModule({ imports: [ NoopAnimationsModule, HttpClientModule, RouterTestingModule, - StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }) + StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }), + EffectsModule.forRoot([]) ], declarations: [ TranslatePipeMock