diff --git a/apps/twitch-logs/src/app/app.module.ts b/apps/twitch-logs/src/app/app.module.ts
index c07ad01..94803e7 100644
--- a/apps/twitch-logs/src/app/app.module.ts
+++ b/apps/twitch-logs/src/app/app.module.ts
@@ -25,8 +25,10 @@ import { FlexLayoutModule } from "@angular/flex-layout";
import { AppComponent } from './app.component';
import { VideosComponent } from './videos/videos.component';
import { CommentsComponent } from './comments/comments.component';
+import { SearchComponent } from './search/search.component';
import { SplitBadgesPipe } from './pipes/splitbadges.pipe';
import { SplitMessagePipe } from './pipes/splitmessage.pipe';
+import { ToTimePipe } from './pipes/totime.pipe';
import { ToHMSPipe } from './pipes/tohms.pipe';
import { CommentsService } from './services/comments.service';
import { ImagesService } from './services/images.service';
@@ -35,6 +37,7 @@ import { ImagesService } from './services/images.service';
const routes: Routes = [
{ path: '', component: VideosComponent },
{ path: 'videos/:id', component: CommentsComponent },
+ { path: 'search', component: SearchComponent },
{ path: '**', redirectTo: '/' }
];
@@ -43,8 +46,10 @@ const routes: Routes = [
AppComponent,
VideosComponent,
CommentsComponent,
+ SearchComponent,
SplitBadgesPipe,
SplitMessagePipe,
+ ToTimePipe,
ToHMSPipe
],
imports: [
diff --git a/apps/twitch-logs/src/app/comments/comments.component.html b/apps/twitch-logs/src/app/comments/comments.component.html
index 5a37dd5..2945f9e 100644
--- a/apps/twitch-logs/src/app/comments/comments.component.html
+++ b/apps/twitch-logs/src/app/comments/comments.component.html
@@ -2,15 +2,15 @@
-
+
-
+
-
+
@@ -25,13 +25,13 @@
- Offset
+ Time
- {{comment.offset | tohms : 'colons'}}
+ {{comment.offset | totime : comment.video_recorded_at | tohms : 'colons'}}
diff --git a/apps/twitch-logs/src/app/models/comment.ts b/apps/twitch-logs/src/app/models/comment.ts
index ba6c15e..0bed9ef 100644
--- a/apps/twitch-logs/src/app/models/comment.ts
+++ b/apps/twitch-logs/src/app/models/comment.ts
@@ -1,6 +1,7 @@
export interface Comment {
id: string;
video_id: number;
+ video_recorded_at: Date;
offset: number;
commenter_id: string;
commenter_name: string;
diff --git a/apps/twitch-logs/src/app/models/searchresult.ts b/apps/twitch-logs/src/app/models/searchresult.ts
new file mode 100644
index 0000000..81492c3
--- /dev/null
+++ b/apps/twitch-logs/src/app/models/searchresult.ts
@@ -0,0 +1,22 @@
+export interface SearchResult {
+ id: string;
+ video_id: number;
+ video_title: string;
+ video_game: string;
+ video_length: number;
+ video_thumbnail_small: string;
+ video_thumbnail_medium: string;
+ video_thumbnail_large: string;
+ video_recorded_at: Date;
+ offset: number;
+ commenter_id: string;
+ commenter_name: string;
+ commenter_display_name: string;
+ commenter_logo: string;
+ source: string;
+ message_body: string;
+ message_user_color: string;
+ message_user_badges: string;
+ created_at: Date;
+ updated_at: Date;
+}
diff --git a/apps/twitch-logs/src/app/pipes/totime.pipe.ts b/apps/twitch-logs/src/app/pipes/totime.pipe.ts
new file mode 100644
index 0000000..9f88254
--- /dev/null
+++ b/apps/twitch-logs/src/app/pipes/totime.pipe.ts
@@ -0,0 +1,19 @@
+import {
+ Pipe,
+ PipeTransform
+} from '@angular/core';
+
+
+@Pipe({
+ name: 'totime'
+})
+export class ToTimePipe implements PipeTransform {
+
+ transform(offset: number, base: string): number {
+ let recorded = new Date(base + 'Z');
+ let result = new Date(recorded.getTime() + offset * 1000);
+ let ref = new Date(result.getTime());
+ ref.setHours(0, 0, 0, 0);
+ return (result.getTime() - ref.getTime()) / 1000 >> 0;
+ }
+}
diff --git a/apps/twitch-logs/src/app/search/search.component.css b/apps/twitch-logs/src/app/search/search.component.css
new file mode 100644
index 0000000..01a13fa
--- /dev/null
+++ b/apps/twitch-logs/src/app/search/search.component.css
@@ -0,0 +1,50 @@
+.comments {
+ text-align: center;
+}
+
+.comments-table {
+ text-align: left;
+}
+
+.mat-column-video_title {
+ white-space: nowrap;
+}
+
+.mat-column-video_title img, .mat-column-video_title a {
+ vertical-align: middle;
+}
+
+.mat-column-commenter_display_name {
+ font-weight: 500;
+}
+
+.mat-column-commenter_display_name img {
+ vertical-align: middle;
+}
+
+.mat-column-commenter_display_name span.commenter {
+ vertical-align: middle;
+}
+
+.mat-column-message_body span, .mat-column-message_body img {
+ vertical-align: middle;
+}
+
+.minispacer {
+ flex: 0.1 1 auto;
+}
+
+.spacer {
+ flex: 1 1 auto;
+}
+
+.spinner-container {
+ position: fixed;
+ left: 50%;
+ top: 15%;
+ margin-left: -50px;
+}
+
+.spinner-container mat-spinner {
+ margin: 6em auto 0 auto;
+}
diff --git a/apps/twitch-logs/src/app/search/search.component.html b/apps/twitch-logs/src/app/search/search.component.html
new file mode 100644
index 0000000..75dca0a
--- /dev/null
+++ b/apps/twitch-logs/src/app/search/search.component.html
@@ -0,0 +1,129 @@
+
diff --git a/apps/twitch-logs/src/app/search/search.component.ts b/apps/twitch-logs/src/app/search/search.component.ts
new file mode 100644
index 0000000..17242d3
--- /dev/null
+++ b/apps/twitch-logs/src/app/search/search.component.ts
@@ -0,0 +1,124 @@
+import {
+ AfterViewInit,
+ Component,
+ ElementRef,
+ OnDestroy,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { ActivatedRoute } from '@angular/router';
+
+import {
+ MatPaginator,
+ MatSort,
+ MatTableDataSource
+} from '@angular/material';
+
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import {
+ debounceTime,
+ delay,
+ distinctUntilChanged,
+ startWith,
+ tap
+} from 'rxjs/operators';
+
+import { merge } from 'rxjs/observable/merge';
+import { fromEvent } from 'rxjs/observable/fromEvent';
+
+import { CommentsService } from '../services/comments.service';
+import { ImagesService } from '../services/images.service';
+import { SearchDataSource } from '../services/search.datasource';
+
+
+@Component({
+ selector: 'search',
+ templateUrl: './search.component.html',
+ styleUrls: [ './search.component.css' ]
+})
+export class SearchComponent implements OnInit, OnDestroy, AfterViewInit {
+ private badgesSubject = new BehaviorSubject([]);
+ private emotesSubject = new BehaviorSubject([]);
+
+ dataSource: SearchDataSource;
+
+ displayedColumns = [
+ 'video_title',
+ 'video_recorded_at',
+ 'offset',
+ 'commenter_display_name',
+ 'message_body'
+ ];
+
+ @ViewChild(MatPaginator) paginator: MatPaginator;
+ @ViewChild(MatSort) sort: MatSort;
+ @ViewChild('input1') input1: ElementRef;
+ @ViewChild('input2') input2: ElementRef;
+
+ public badges$ = this.badgesSubject.asObservable();
+ public emotes$ = this.emotesSubject.asObservable();
+
+ constructor(private route: ActivatedRoute,
+ private commentsService: CommentsService,
+ private imagesService: ImagesService) { }
+
+ ngOnInit() {
+ this.dataSource = new SearchDataSource(this.commentsService);
+ this.dataSource.loadComments('', '', 'video_recorded_at', 'desc', 0, 20);
+ this.imagesService.getBadges().subscribe(
+ (data: any[]) => this.badgesSubject.next(data)
+ );
+ this.imagesService.getEmotes().subscribe(
+ (data: any[]) => this.emotesSubject.next(data)
+ );
+ }
+
+ ngOnDestroy() {
+ this.badgesSubject.complete();
+ this.emotesSubject.complete();
+ }
+
+ ngAfterViewInit() {
+ this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
+
+ fromEvent(this.input1.nativeElement, 'keyup')
+ .pipe(
+ debounceTime(300),
+ distinctUntilChanged(),
+ tap(() => {
+ this.paginator.pageIndex = 0;
+ this.loadCommentsPage();
+ })
+ )
+ .subscribe();
+
+ fromEvent(this.input2.nativeElement, 'keyup')
+ .pipe(
+ debounceTime(300),
+ distinctUntilChanged(),
+ tap(() => {
+ this.paginator.pageIndex = 0;
+ this.loadCommentsPage();
+ })
+ )
+ .subscribe();
+
+ merge(this.sort.sortChange, this.paginator.page)
+ .pipe(
+ tap(() => this.loadCommentsPage())
+ )
+ .subscribe();
+ }
+
+ loadCommentsPage() {
+ this.dataSource.loadComments(
+ this.input1.nativeElement.value,
+ this.input2.nativeElement.value,
+ this.sort.active,
+ this.sort.direction,
+ this.paginator.pageIndex,
+ this.paginator.pageSize);
+ }
+}
diff --git a/apps/twitch-logs/src/app/services/comments.service.ts b/apps/twitch-logs/src/app/services/comments.service.ts
index 00e7203..0d7dcd7 100644
--- a/apps/twitch-logs/src/app/services/comments.service.ts
+++ b/apps/twitch-logs/src/app/services/comments.service.ts
@@ -11,6 +11,7 @@ import { map } from 'rxjs/operators';
import { Video } from '../models/video';
import { Comment } from '../models/comment';
+import { SearchResult } from '../models/searchresult';
@Injectable()
@@ -56,4 +57,25 @@ export class CommentsService {
}))
);
}
+
+ searchComments(
+ commenter = '', term = '', sortBy = 'video_recorded_at', sortOrder = 'desc',
+ pageNumber = 0, pageSize = 20): Observable {
+
+ return this.http.get('/twitch-logs/api/search', {
+ observe: 'response',
+ params: new HttpParams()
+ .set('commenter', commenter)
+ .set('term', term)
+ .set('sort_by', sortBy)
+ .set('sort_order', sortOrder)
+ .set('page_number', pageNumber.toString())
+ .set('page_size', pageSize.toString())
+ }).pipe(
+ map((res: any) => ({
+ results: res.body,
+ totalCount: parseInt(res.headers.get('X-Total-Count'))
+ }))
+ );
+ }
}
diff --git a/apps/twitch-logs/src/app/services/search.datasource.ts b/apps/twitch-logs/src/app/services/search.datasource.ts
new file mode 100644
index 0000000..6ef994a
--- /dev/null
+++ b/apps/twitch-logs/src/app/services/search.datasource.ts
@@ -0,0 +1,61 @@
+import {
+ CollectionViewer,
+ DataSource
+} from '@angular/cdk/collections';
+
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { of } from 'rxjs/observable/of';
+
+import {
+ catchError,
+ finalize
+} from 'rxjs/operators';
+
+import { CommentsService } from './comments.service';
+
+import { SearchResult } from '../models/searchresult';
+
+
+export class SearchDataSource implements DataSource {
+ private resultsSubject = new BehaviorSubject([]);
+ private countSubject = new BehaviorSubject(0);
+ private loadingSubject = new BehaviorSubject(false);
+
+ public count$ = this.countSubject.asObservable();
+ public loading$ = this.loadingSubject.asObservable();
+
+ constructor(private commentsService: CommentsService) { }
+
+ loadComments(commenter: string,
+ term: string,
+ sortColumn: string,
+ sortDirection: string,
+ pageIndex: number,
+ pageSize: number) {
+
+ this.loadingSubject.next(true);
+
+ this.commentsService.searchComments(commenter, term, sortColumn, sortDirection,
+ pageIndex, pageSize)
+ .pipe(
+ catchError(() => of([])),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe((data: any) => {
+ this.resultsSubject.next(data.results);
+ this.countSubject.next(data.totalCount);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable {
+ console.log('Connecting data source');
+ return this.resultsSubject.asObservable();
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.resultsSubject.complete();
+ this.countSubject.complete();
+ this.loadingSubject.complete();
+ }
+}
diff --git a/apps/twitch-logs/src/app/videos/videos.component.html b/apps/twitch-logs/src/app/videos/videos.component.html
index d2878b1..df651c5 100644
--- a/apps/twitch-logs/src/app/videos/videos.component.html
+++ b/apps/twitch-logs/src/app/videos/videos.component.html
@@ -2,14 +2,16 @@
-
+
-
+
+
+