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;