Merge remote-tracking branch 'origin/test'
commit
9e7f02e2be
|
|
@ -19,6 +19,7 @@ import { ContributorAllReportsComponent } from './components/contributor-all-rep
|
||||||
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
||||||
import { VideoServicesComponent } from './components/video-services/video-services.component';
|
import { VideoServicesComponent } from './components/video-services/video-services.component';
|
||||||
import { ImageComponent } from './components/image/image.component';
|
import { ImageComponent } from './components/image/image.component';
|
||||||
|
import { VideoComponent } from './components/video/video.component';
|
||||||
|
|
||||||
const routes =
|
const routes =
|
||||||
[
|
[
|
||||||
|
|
@ -52,7 +53,7 @@ const routes =
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'video',
|
path: 'video',
|
||||||
component: VideoServicesComponent
|
component: VideoComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'sermons/:id',
|
path: 'sermons/:id',
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,11 @@ import { ContributorAllReportsComponent } from './components/contributor-all-rep
|
||||||
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
||||||
import { VideoServicesComponent } from './components/video-services/video-services.component';
|
import { VideoServicesComponent } from './components/video-services/video-services.component';
|
||||||
import { ImageComponent } from './components/image/image.component';
|
import { ImageComponent } from './components/image/image.component';
|
||||||
|
import { YoutubeListComponent } from './components/youtube-list/youtube-list.component';
|
||||||
|
import { VideoComponent } from './components/video/video.component';
|
||||||
|
import { YoutubeListService } from './components/youtube-list/youtube-list-service';
|
||||||
|
import { VideoItemComponent } from './components/video-item/video-item.component';
|
||||||
|
import { YoutubePopupComponent } from './components/popups/youtube-popup/youtube-popup.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,7 +134,11 @@ import { ImageComponent } from './components/image/image.component';
|
||||||
ContributorYearlyReportComponent,
|
ContributorYearlyReportComponent,
|
||||||
ContributorAllReportsComponent,
|
ContributorAllReportsComponent,
|
||||||
LiveStreamComponent,
|
LiveStreamComponent,
|
||||||
ImageComponent
|
ImageComponent,
|
||||||
|
YoutubeListComponent,
|
||||||
|
VideoComponent,
|
||||||
|
VideoItemComponent,
|
||||||
|
YoutubePopupComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|
@ -150,7 +159,7 @@ import { ImageComponent } from './components/image/image.component';
|
||||||
MatRadioModule,
|
MatRadioModule,
|
||||||
MatCheckboxModule
|
MatCheckboxModule
|
||||||
],
|
],
|
||||||
providers: [LoginService,PrintService,UserService,GoogleAnalyticsService,SermonService,TransactionService,EventService,EmailService,MissionarySupportService,WindowRefService],
|
providers: [LoginService,PrintService,UserService,GoogleAnalyticsService,SermonService,TransactionService,EventService,EmailService,MissionarySupportService,YoutubeListService,WindowRefService],
|
||||||
entryComponents: [AddSermonPopupComponent,
|
entryComponents: [AddSermonPopupComponent,
|
||||||
LoginPopupComponent,
|
LoginPopupComponent,
|
||||||
OkPopupComponent,
|
OkPopupComponent,
|
||||||
|
|
@ -161,7 +170,8 @@ import { ImageComponent } from './components/image/image.component';
|
||||||
AddEventPopupComponent,
|
AddEventPopupComponent,
|
||||||
AddUserPopupComponent,
|
AddUserPopupComponent,
|
||||||
VideoPopupComponent,
|
VideoPopupComponent,
|
||||||
AddTransactionPopupComponent],
|
AddTransactionPopupComponent,
|
||||||
|
YoutubePopupComponent],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<p ofbFadeInOnScroll class="action">
|
<p ofbFadeInOnScroll class="action">
|
||||||
<i ofbicon>live_tv</i> <a routerLink="/live" class="align-top">Click here to watch online</a>
|
<i ofbicon>live_tv</i> <a routerLink="/video" class="align-top">Click here to watch online</a>
|
||||||
</p>
|
</p>
|
||||||
<p ofbFadeInOnScroll class="action">
|
<p ofbFadeInOnScroll class="action">
|
||||||
<i ofbicon>headset</i> <a routerLink="/sermons" class="align-top">Click here to listen online</a>
|
<i ofbicon>headset</i> <a routerLink="/sermons" class="align-top">Click here to listen online</a>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ export class SharePopupComponent implements OnInit {
|
||||||
if (this.data.prefix === 'o') {
|
if (this.data.prefix === 'o') {
|
||||||
this.shareUrl = this.document.location.protocol + '//' + this.document.location.hostname + port + this.shareBaseUrl + data.prefix + data.otherName;
|
this.shareUrl = this.document.location.protocol + '//' + this.document.location.hostname + port + this.shareBaseUrl + data.prefix + data.otherName;
|
||||||
}
|
}
|
||||||
|
if (this.data.url) {
|
||||||
|
this.shareUrl = this.data.url;
|
||||||
|
}
|
||||||
this.facebookIframeUrl = this.urlPartA + this.shareUrl + this.urlPartB;
|
this.facebookIframeUrl = this.urlPartA + this.shareUrl + this.urlPartB;
|
||||||
this.twitterUrl = this.twitterPartA + data.title + " - " + data.description + "&url=" + this.shareUrl;
|
this.twitterUrl = this.twitterPartA + data.title + " - " + data.description + "&url=" + this.shareUrl;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
width: calc(100% - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr{
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 24pt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-align-top {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div md-dialog-title>
|
||||||
|
<h2>{{title}}</h2>
|
||||||
|
<button mat-icon-button (click)="close()">
|
||||||
|
<i ofbicon>close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr color="primary">
|
||||||
|
<div md-dialog-content>
|
||||||
|
<iframe width="560" height="315" [src]="embedUrl | safeUrl" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { YoutubePopupComponent } from './youtube-popup.component';
|
||||||
|
|
||||||
|
describe('YoutubePopupComponent', () => {
|
||||||
|
let component: YoutubePopupComponent;
|
||||||
|
let fixture: ComponentFixture<YoutubePopupComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ YoutubePopupComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(YoutubePopupComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Component, OnInit, Inject } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-youtube-popup',
|
||||||
|
templateUrl: './youtube-popup.component.html',
|
||||||
|
styleUrls: ['./youtube-popup.component.css']
|
||||||
|
})
|
||||||
|
export class YoutubePopupComponent implements OnInit {
|
||||||
|
|
||||||
|
public title: string;
|
||||||
|
public embedUrl: string;
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: any, private MatDialogRef: MatDialogRef<YoutubePopupComponent>) {
|
||||||
|
this.title = data.title;
|
||||||
|
this.embedUrl = data.embedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
this.MatDialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
.wrapper{
|
||||||
|
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 180px;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade{
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: bottom;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 20px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffffff+0,ffffff+100&0+0,0.8+100 */
|
||||||
|
background: -moz-linear-gradient(top, rgba(255,255,255,0) 0%, rgba(255,255,255,0.8) 100%); /* FF3.6-15 */
|
||||||
|
background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%); /* Chrome10-25,Safari5.1-6 */
|
||||||
|
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ccffffff',GradientType=0 ); /* IE6-9 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo button{
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
button img{
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info{
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 2px 2px 8px 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: calc(100% - 180px);
|
||||||
|
overflow-y: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date, .author{
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description{
|
||||||
|
font-size: 14px;
|
||||||
|
color: darkgray;
|
||||||
|
word-wrap: none;
|
||||||
|
border-top: 0px solid lightgray;
|
||||||
|
padding-top:6px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.expanded{
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.expanded-content{
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons, .buttons-edit{
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons button, .buttons .filler{
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border{
|
||||||
|
margin: 0px 5px 0px 5px;
|
||||||
|
border-top: 1px inset lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-edit button{
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="wrapper" [@inout]="startFadeIn">
|
||||||
|
<div class="photo">
|
||||||
|
<button mat-button (click)="play()">
|
||||||
|
<img src="{{thumbnailUrl}}" />
|
||||||
|
</button>
|
||||||
|
</div><!--
|
||||||
|
--><div class="info" (click)="toggleState()">
|
||||||
|
<span class="title">{{ title }}</span>
|
||||||
|
<br>
|
||||||
|
<span class="date">{{ publishTime | ofbDate:true }}</span>
|
||||||
|
<br>
|
||||||
|
<p class="description" [@toggleAnimationFade]="state">
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
<div class="fade"><i ofbicon *ngIf="state === 'closed'">arrow_drop_down</i><i ofbicon *ngIf="state === 'open'">arrow_drop_up</i></div>
|
||||||
|
</div>
|
||||||
|
<div class="expanded" [@toggleAnimation]="state" (click)="toggleState()">
|
||||||
|
<p class="expanded-content">
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<button mat-button class="action pct50" (click)="play()" ><i ofbicon>tv</i> Play</button><!--
|
||||||
|
--><button mat-button class="action pct50" (click)="share()" ><i ofbicon>share</i> Share</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { VideoItemComponent } from './video-item.component';
|
||||||
|
|
||||||
|
describe('VideoItemComponent', () => {
|
||||||
|
let component: VideoItemComponent;
|
||||||
|
let fixture: ComponentFixture<VideoItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ VideoItemComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(VideoItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Component, AfterContentInit, Input, Inject } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogConfig } from '@angular/material';
|
||||||
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
|
|
||||||
|
import { SharePopupComponent } from './../popups/share-popup/share-popup.component';
|
||||||
|
import { YesNoPopupComponent } from './../popups/yes-no-popup/yes-no-popup.component';
|
||||||
|
import { YoutubePopupComponent } from '../popups/youtube-popup/youtube-popup.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-video-item',
|
||||||
|
templateUrl: './video-item.component.html',
|
||||||
|
styleUrls: ['./video-item.component.css'],
|
||||||
|
animations:[
|
||||||
|
trigger("inout",[
|
||||||
|
state("1", style({ opacity: '1' })),
|
||||||
|
state("0",style({ opacity: '0' })),
|
||||||
|
transition('* => 1',[
|
||||||
|
animate('500ms ease-in-out')
|
||||||
|
]),
|
||||||
|
transition(':enter',[
|
||||||
|
animate('0ms')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
trigger('toggleAnimation', [
|
||||||
|
state('open', style({
|
||||||
|
height: '*',
|
||||||
|
})),
|
||||||
|
state('closed', style({
|
||||||
|
height: '0px',
|
||||||
|
})),
|
||||||
|
transition('open <=> closed', animate('500ms ease-in-out'))
|
||||||
|
]),
|
||||||
|
trigger('toggleAnimationFade', [
|
||||||
|
state('open', style({
|
||||||
|
opacity: 0,
|
||||||
|
})),
|
||||||
|
state('closed', style({
|
||||||
|
opacity: 1,
|
||||||
|
})),
|
||||||
|
transition('open <=> closed', animate('500ms ease-in-out'))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class VideoItemComponent implements AfterContentInit {
|
||||||
|
|
||||||
|
public state: string = 'closed';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
id: number;
|
||||||
|
@Input()
|
||||||
|
title: string;
|
||||||
|
@Input()
|
||||||
|
description: string;
|
||||||
|
@Input()
|
||||||
|
publishTime: Date;
|
||||||
|
@Input()
|
||||||
|
thumbnailUrl: string;
|
||||||
|
@Input()
|
||||||
|
shareUrl: string;
|
||||||
|
@Input()
|
||||||
|
embedUrl: string;
|
||||||
|
public startFadeIn: boolean = false;
|
||||||
|
@Input()
|
||||||
|
public delayFadeIn: number = 100;
|
||||||
|
|
||||||
|
constructor(private dialog: MatDialog){
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterContentInit(): void{
|
||||||
|
setTimeout(() => this.startFadeIn = true, this.delayFadeIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleState(){
|
||||||
|
this.state = this.state === 'open' ? 'closed' : 'open'
|
||||||
|
}
|
||||||
|
|
||||||
|
share(){
|
||||||
|
let opts = new MatDialogConfig;
|
||||||
|
opts.data = { url: this.shareUrl, id: this.id, title: this.title, description: '' };
|
||||||
|
let dialog = this.dialog.open(SharePopupComponent, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
play(){
|
||||||
|
let opts = new MatDialogConfig;
|
||||||
|
opts.data = { title: this.title, embedUrl:this.embedUrl };
|
||||||
|
let dialog = this.dialog.open(YoutubePopupComponent, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
|
||||||
|
.side-bar{
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width100{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#showAllButton{
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<secondary-page-component [hideSideBarOnMobile]="true" >
|
||||||
|
<div mainContent>
|
||||||
|
<app-youtube-list></app-youtube-list>
|
||||||
|
</div>
|
||||||
|
<div sideBar class="side-bar">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</secondary-page-component>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { VideoComponent } from './video.component';
|
||||||
|
|
||||||
|
describe('VideoComponent', () => {
|
||||||
|
let component: VideoComponent;
|
||||||
|
let fixture: ComponentFixture<VideoComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ VideoComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(VideoComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-video',
|
||||||
|
templateUrl: './video.component.html',
|
||||||
|
styleUrls: ['./video.component.css']
|
||||||
|
})
|
||||||
|
export class VideoComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { YoutubeSearchResult } from './youtube-snippet';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
const apiKey = 'AIzaSyDYI1UzFyc-5UvWiN7BqkWm-8vXU6i5Au0';
|
||||||
|
const baseUrl = `https://www.googleapis.com/youtube/v3/search`;
|
||||||
|
const part = `snippet`;
|
||||||
|
const channelId = `UCplK-bimKe_E8k6Gka87IaQ`;
|
||||||
|
const maxResults = `20`;
|
||||||
|
const order = `date`;
|
||||||
|
const itemType = `video`;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class YoutubeListService {
|
||||||
|
constructor(private httpClient: HttpClient){}
|
||||||
|
private nextPageToken: string;
|
||||||
|
|
||||||
|
public getFirstPage() : Observable<YoutubeSearchResult> {
|
||||||
|
const url = this.getUrl(undefined);
|
||||||
|
return this.httpClient.get<YoutubeSearchResult>(url).pipe(tap(x => this.nextPageToken = x.nextPageToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextPage() : Observable<YoutubeSearchResult> {
|
||||||
|
const url = this.getUrl(this.nextPageToken);
|
||||||
|
return this.httpClient.get<YoutubeSearchResult>(url).pipe(tap(x => this.nextPageToken = x.nextPageToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUrl(nextPageToken: string): string {
|
||||||
|
let url = `${baseUrl}?type=${itemType}&part=${part}&channelId=${channelId}&maxResults=${maxResults}&order=${order}&key=${apiKey}`;
|
||||||
|
if (nextPageToken) {
|
||||||
|
url = url + `&pageToken=${nextPageToken}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
.width100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul{
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li{
|
||||||
|
margin: 40px 0px 40px 0px; /* top right bottom left */
|
||||||
|
}
|
||||||
|
|
||||||
|
li:first-child{
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:last-child{
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let item of items">
|
||||||
|
<app-video-item
|
||||||
|
[title]="item.snippet.title"
|
||||||
|
[description]="item.snippet.description"
|
||||||
|
[publishTime]="item.snippet.publishTime"
|
||||||
|
[thumbnailUrl]="item.snippet.thumbnails.medium.url"
|
||||||
|
shareUrl="https://youtu.be/{{item.id.videoId}}"
|
||||||
|
embedUrl="https://www.youtube.com/embed/{{item.id.videoId}}">
|
||||||
|
|
||||||
|
</app-video-item>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button *ngIf="showLoadMore()" mat-button [disabled]="loading" class="width100 mt-20" style="text-align: center;" (click)="loadMore()">
|
||||||
|
<i ofbicon *ngIf="loading">access_time</i><span *ngIf="loading"> Loading...</span>
|
||||||
|
<i ofbicon *ngIf="!loading">expand_more</i><span *ngIf="!loading"> Load More</span>
|
||||||
|
</button>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { YoutubeListComponent } from './youtube-list.component';
|
||||||
|
|
||||||
|
describe('YoutubeListComponent', () => {
|
||||||
|
let component: YoutubeListComponent;
|
||||||
|
let fixture: ComponentFixture<YoutubeListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ YoutubeListComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(YoutubeListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { YoutubeListService } from './youtube-list-service';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { YoutubeSnippet, YoutubeSnippetItem, YoutubeSearchResult } from './youtube-snippet';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-youtube-list',
|
||||||
|
templateUrl: './youtube-list.component.html',
|
||||||
|
styleUrls: ['./youtube-list.component.css']
|
||||||
|
})
|
||||||
|
export class YoutubeListComponent implements OnInit {
|
||||||
|
public loading: boolean = false;
|
||||||
|
public items: YoutubeSnippetItem[] = [];
|
||||||
|
private totalResults: number = 0;
|
||||||
|
|
||||||
|
constructor(private youtube: YoutubeListService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loading = true;
|
||||||
|
this.youtube.getFirstPage().pipe(take(1)).subscribe(x => {
|
||||||
|
this.totalResults = x.pageInfo.totalResults;
|
||||||
|
this.processResults(x);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public showLoadMore() {
|
||||||
|
if (this.items.length < this.totalResults && this.loading === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadMore() {
|
||||||
|
if (this.items.length >= this.totalResults) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
this.youtube.getNextPage().pipe(take(1)).subscribe(x => {
|
||||||
|
this.processResults(x);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private processResults(res: YoutubeSearchResult) {
|
||||||
|
var parser = new DOMParser;
|
||||||
|
res.items.forEach(x => x.snippet.title = parser.parseFromString(x.snippet.title, "text/html").body.innerHTML);
|
||||||
|
if (this.items.length === 0) {
|
||||||
|
this.items = res.items;
|
||||||
|
} else {
|
||||||
|
for(let i = 0; i < res.items.length; i++) {
|
||||||
|
this.items.push(res.items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
export class YoutubeSearchResult {
|
||||||
|
public kind: string;
|
||||||
|
public etag: string;
|
||||||
|
public nextPageToken: string;
|
||||||
|
public regionCode: string;
|
||||||
|
public pageInfo: YoutubeSearchResultPageInfo;
|
||||||
|
public items: YoutubeSnippetItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeSearchResultPageInfo {
|
||||||
|
public totalResults: number;
|
||||||
|
public resultsPerPage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeSnippetItem {
|
||||||
|
public kind: string;
|
||||||
|
public etag: string;
|
||||||
|
public id: YoutubeSnippetId;
|
||||||
|
public snippet: YoutubeSnippet
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeSnippetId {
|
||||||
|
public kind: string;
|
||||||
|
public videoId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeSnippet {
|
||||||
|
public publishedAt: Date;
|
||||||
|
public channelId: string;
|
||||||
|
public title: string;
|
||||||
|
public description: string;
|
||||||
|
public channelTitle: string;
|
||||||
|
public liveBroadcastContent: string;
|
||||||
|
public publishTime: Date;
|
||||||
|
public thumbnails: YoutubeSnippetThumbnails;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeSnippetThumbnails {
|
||||||
|
public default: YoutubeThumbnail;
|
||||||
|
public medium: YoutubeThumbnail;
|
||||||
|
public high: YoutubeThumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YoutubeThumbnail {
|
||||||
|
public url: string;
|
||||||
|
public width: number;
|
||||||
|
public height: number;
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,9 @@ export class OfbDatePipe implements PipeTransform {
|
||||||
console.error("value for ofb-date-pipe was undefined or null");
|
console.error("value for ofb-date-pipe was undefined or null");
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = new Date(value);
|
||||||
|
}
|
||||||
var year = value.getFullYear();
|
var year = value.getFullYear();
|
||||||
var month = value.getMonth() + 1;
|
var month = value.getMonth() + 1;
|
||||||
var day = value.getDate();
|
var day = value.getDate();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,42 @@
|
||||||
{
|
{
|
||||||
"videos": [
|
"videos": [
|
||||||
|
{
|
||||||
|
"isReady": true,
|
||||||
|
"title": "Sunday Evening",
|
||||||
|
"src": "",
|
||||||
|
"yt": "https://www.youtube.com/embed/MTej_Yk5py0",
|
||||||
|
"date": "2020-05-17T19:00:00-06:00",
|
||||||
|
"archived": false,
|
||||||
|
"message": "Please check back later for the Sunday Evening Service"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"isReady": true,
|
||||||
|
"title": "Sunday Morning",
|
||||||
|
"src": "",
|
||||||
|
"yt": "https://www.youtube.com/embed/7t48zsLmZvM",
|
||||||
|
"date": "2020-05-17T11:00:00-06:00",
|
||||||
|
"archived": true,
|
||||||
|
"message": "Please check back later for the Sunday Morning Service"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"isReady": true,
|
||||||
|
"title": "Wednesday Evening",
|
||||||
|
"src": "",
|
||||||
|
"yt": "https://www.youtube.com/embed/eUOQ4Cm-eKM",
|
||||||
|
"date": "2020-05-13T19:00:00-06:00",
|
||||||
|
"archived": true,
|
||||||
|
"message": "Please check back later for the Wednesday Evening Service"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"isReady": true,
|
"isReady": true,
|
||||||
"title": "Sunday Evening",
|
"title": "Sunday Evening",
|
||||||
"src": "",
|
"src": "",
|
||||||
"yt": "https://www.youtube.com/embed/wCqhj3HnhKU",
|
"yt": "https://www.youtube.com/embed/wCqhj3HnhKU",
|
||||||
"date": "2020-05-10T19:00:00-06:00",
|
"date": "2020-05-10T19:00:00-06:00",
|
||||||
"archived": false,
|
"archived": true,
|
||||||
"message": "Please check back later for the Sunday Evening Service"
|
"message": "Please check back later for the Sunday Evening Service"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue