From 6de7de72241b895836695e486bbce359f6eef850 Mon Sep 17 00:00:00 2001 From: benjamin Date: Thu, 28 Nov 2019 20:01:30 +0100 Subject: [PATCH] searchbox --- src/app/animations.ts | 20 +++++++++ src/app/app.module.ts | 1 + .../application-frame.module.ts | 4 +- .../navigation/filter/filter.component.html | 2 + .../navigation/filter/filter.component.less | 16 +++++++ .../filter/filter.component.spec.ts | 25 +++++++++++ .../navigation/filter/filter.component.ts | 21 ++++++++++ .../navigation/navigation.component.html | 6 ++- .../navigation/navigation.component.less | 7 ++++ src/app/songs/services/song.service.spec.ts | 2 +- src/app/songs/services/song.service.ts | 2 +- .../list-item/list-item.component.less | 1 + .../songs/song-list/song-list.component.html | 4 +- .../song-list/song-list.component.spec.ts | 2 +- .../songs/song-list/song-list.component.ts | 42 +++++++++++++++---- src/styles/styles.less | 2 +- 16 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/app/animations.ts create mode 100644 src/app/application-frame/navigation/filter/filter.component.html create mode 100644 src/app/application-frame/navigation/filter/filter.component.less create mode 100644 src/app/application-frame/navigation/filter/filter.component.spec.ts create mode 100644 src/app/application-frame/navigation/filter/filter.component.ts diff --git a/src/app/animations.ts b/src/app/animations.ts new file mode 100644 index 0000000..77f3bf2 --- /dev/null +++ b/src/app/animations.ts @@ -0,0 +1,20 @@ +import {animate, query, stagger, state, style, transition, trigger} from '@angular/animations'; + +export const fade = [ + // the fade-in/fade-out animation. + trigger('fade', [ + + // the "in" style determines the "resting" state of the element when it is visible. + state('in', style({opacity: 1, display: 'block', transform: 'translateY(0px)'})), + + // fade in when created. this could also be written as transition('void => *') + transition(':enter', [ + style({opacity: 0, display: 'block', transform: 'translateY(-20px)'}), + animate(300) + ]), + + // fade out when destroyed. this could also be written as transition('void => *') + transition(':leave', + animate(300, style({opacity: 0, display: 'block', transform: 'translateY(-20px)'}))) + ]) +]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3d63b42..13c4a59 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -16,6 +16,7 @@ import {AngularFirestoreModule} from '@angular/fire/firestore'; ], imports: [ BrowserModule, + BrowserAnimationsModule, AppRoutingModule, ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}), BrowserAnimationsModule, diff --git a/src/app/application-frame/application-frame.module.ts b/src/app/application-frame/application-frame.module.ts index 393bec7..af1df32 100644 --- a/src/app/application-frame/application-frame.module.ts +++ b/src/app/application-frame/application-frame.module.ts @@ -2,11 +2,13 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {NavigationComponent} from './navigation/navigation.component'; import {RouterModule} from '@angular/router'; +import {FilterComponent} from './navigation/filter/filter.component'; @NgModule({ declarations: [ - NavigationComponent + NavigationComponent, + FilterComponent ], imports: [ CommonModule, diff --git a/src/app/application-frame/navigation/filter/filter.component.html b/src/app/application-frame/navigation/filter/filter.component.html new file mode 100644 index 0000000..de22368 --- /dev/null +++ b/src/app/application-frame/navigation/filter/filter.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/application-frame/navigation/filter/filter.component.less b/src/app/application-frame/navigation/filter/filter.component.less new file mode 100644 index 0000000..eb3e1f2 --- /dev/null +++ b/src/app/application-frame/navigation/filter/filter.component.less @@ -0,0 +1,16 @@ +@import "../../../../styles/styles"; + +input { + font-size: 16px; + background: transparent; + border: none; + border-bottom: 1px solid #ccc; + color: #888; + margin-right: 20px; + + &:focus { + outline: none; + border-bottom: 2px solid @primary-color; + margin-bottom: -1px; + } +} diff --git a/src/app/application-frame/navigation/filter/filter.component.spec.ts b/src/app/application-frame/navigation/filter/filter.component.spec.ts new file mode 100644 index 0000000..fc30049 --- /dev/null +++ b/src/app/application-frame/navigation/filter/filter.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilterComponent } from './filter.component'; + +describe('FilterComponent', () => { + let component: FilterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FilterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/application-frame/navigation/filter/filter.component.ts b/src/app/application-frame/navigation/filter/filter.component.ts new file mode 100644 index 0000000..5b1c61c --- /dev/null +++ b/src/app/application-frame/navigation/filter/filter.component.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; +import {Router} from '@angular/router'; + +@Component({ + selector: 'app-filter', + templateUrl: './filter.component.html', + styleUrls: ['./filter.component.less'] +}) +export class FilterComponent { + + constructor(private router: Router) { + } + + public onInputChange(text: string): void { + const route = text + ? this.router.createUrlTree(['songs'], {queryParams: {q: text}}) + : this.router.createUrlTree(['songs']); + + this.router.navigateByUrl(route); + } +} diff --git a/src/app/application-frame/navigation/navigation.component.html b/src/app/application-frame/navigation/navigation.component.html index f6cc5ca..d8c1abf 100644 --- a/src/app/application-frame/navigation/navigation.component.html +++ b/src/app/application-frame/navigation/navigation.component.html @@ -1,5 +1,9 @@ diff --git a/src/app/application-frame/navigation/navigation.component.less b/src/app/application-frame/navigation/navigation.component.less index 323492e..d8e9201 100644 --- a/src/app/application-frame/navigation/navigation.component.less +++ b/src/app/application-frame/navigation/navigation.component.less @@ -13,6 +13,7 @@ nav { display: flex; align-items: flex-end; + justify-content: space-between; a { display: block; @@ -38,3 +39,9 @@ nav { } } + +.actions { + display: flex; + height: 100%; + align-items: center; +} diff --git a/src/app/songs/services/song.service.spec.ts b/src/app/songs/services/song.service.spec.ts index fcacd50..1523363 100644 --- a/src/app/songs/services/song.service.spec.ts +++ b/src/app/songs/services/song.service.spec.ts @@ -27,7 +27,7 @@ describe('SongService', () => { it('should list songs', async(() => { const service: SongService = TestBed.get(SongService); - service.list().subscribe(songs => { + service.list$().subscribe(songs => { expect(songs).toEqual([ {title: 'title1'} ]); diff --git a/src/app/songs/services/song.service.ts b/src/app/songs/services/song.service.ts index 5f88479..4a444a6 100644 --- a/src/app/songs/services/song.service.ts +++ b/src/app/songs/services/song.service.ts @@ -11,7 +11,7 @@ export class SongService { constructor(private songDataService: SongDataService) { } - public list = (): Observable => this.songDataService.list(); + public list$ = (): Observable => this.songDataService.list(); public read = (songId: string): Observable => this.songDataService.read(songId); } diff --git a/src/app/songs/song-list/list-item/list-item.component.less b/src/app/songs/song-list/list-item/list-item.component.less index bb4506f..3d5a087 100644 --- a/src/app/songs/song-list/list-item/list-item.component.less +++ b/src/app/songs/song-list/list-item/list-item.component.less @@ -14,6 +14,7 @@ &:hover { background: @primary-color; + color: #fff; } } diff --git a/src/app/songs/song-list/song-list.component.html b/src/app/songs/song-list/song-list.component.html index 904c844..d0a3347 100644 --- a/src/app/songs/song-list/song-list.component.html +++ b/src/app/songs/song-list/song-list.component.html @@ -1,3 +1,3 @@ - - + + diff --git a/src/app/songs/song-list/song-list.component.spec.ts b/src/app/songs/song-list/song-list.component.spec.ts index 3b83aac..49485d3 100644 --- a/src/app/songs/song-list/song-list.component.spec.ts +++ b/src/app/songs/song-list/song-list.component.spec.ts @@ -40,7 +40,7 @@ describe('SongListComponent', () => { it('should read songs from SongService', fakeAsync(() => { tick(); - expect(component.songs).toEqual([ + expect(component.songs$).toEqual([ {title: 'title1'} ]); })); diff --git a/src/app/songs/song-list/song-list.component.ts b/src/app/songs/song-list/song-list.component.ts index 23a8ade..e4b4649 100644 --- a/src/app/songs/song-list/song-list.component.ts +++ b/src/app/songs/song-list/song-list.component.ts @@ -1,24 +1,50 @@ import {Component, OnInit} from '@angular/core'; import {SongService} from '../services/song.service'; import {Song} from '../models/song'; -import {map} from 'rxjs/operators'; -import {Observable} from 'rxjs'; +import {debounceTime, map} from 'rxjs/operators'; +import {combineLatest, Observable} from 'rxjs'; +import {fade} from '../../animations'; +import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-songs', templateUrl: './song-list.component.html', - styleUrls: ['./song-list.component.less'] + styleUrls: ['./song-list.component.less'], + animations: [fade] }) export class SongListComponent implements OnInit { - public songs: Observable; + public songs$: Observable; - constructor(private songService: SongService) { + constructor(private songService: SongService, private activatedRoute: ActivatedRoute) { } ngOnInit() { - this.songs = this.songService.list().pipe(map(songs => - songs.sort((a, b) => a.number - b.number) - )); + const filter$ = this.activatedRoute.queryParams.pipe( + debounceTime(300), + map(_ => _.q) + ); + + const songs$ = this.songService.list$().pipe( + map(songs => songs.sort((a, b) => a.number - b.number)), + ); + + this.songs$ = combineLatest([filter$, songs$]).pipe( + map(_ => _[1].filter(song => this.filter(song, _[0]))) + ); } + private filter(song: Song, filterValue: string): boolean { + if (!filterValue) { + return true; + } + + const textMatch = song.text && this.normalize(song.text).indexOf(this.normalize(filterValue)) !== -1; + const titleMatch = song.title && this.normalize(song.title).indexOf(this.normalize(filterValue)) !== -1; + + return textMatch || titleMatch; + } + + private normalize( input: string): string { + return input.toLowerCase().replace(/\s/g, ''); + } } diff --git a/src/styles/styles.less b/src/styles/styles.less index 0082534..8e38a9d 100644 --- a/src/styles/styles.less +++ b/src/styles/styles.less @@ -1,4 +1,4 @@ -@primary-color: #5285ff; +@primary-color: #4286f4; @navigation-background: #fffffff1; @navigation-link-color: #555;