Merge remote-tracking branch 'origin/test'

test
dan 2020-05-18 00:33:04 -06:00
commit 9e7f02e2be
24 changed files with 652 additions and 6 deletions

View File

@ -19,6 +19,7 @@ import { ContributorAllReportsComponent } from './components/contributor-all-rep
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
import { VideoServicesComponent } from './components/video-services/video-services.component';
import { ImageComponent } from './components/image/image.component';
import { VideoComponent } from './components/video/video.component';
const routes =
[
@ -52,7 +53,7 @@ const routes =
},
{
path: 'video',
component: VideoServicesComponent
component: VideoComponent
},
{
path: 'sermons/:id',

View File

@ -79,6 +79,11 @@ import { ContributorAllReportsComponent } from './components/contributor-all-rep
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
import { VideoServicesComponent } from './components/video-services/video-services.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,
ContributorAllReportsComponent,
LiveStreamComponent,
ImageComponent
ImageComponent,
YoutubeListComponent,
VideoComponent,
VideoItemComponent,
YoutubePopupComponent
],
imports: [
BrowserModule,
@ -150,7 +159,7 @@ import { ImageComponent } from './components/image/image.component';
MatRadioModule,
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,
LoginPopupComponent,
OkPopupComponent,
@ -161,7 +170,8 @@ import { ImageComponent } from './components/image/image.component';
AddEventPopupComponent,
AddUserPopupComponent,
VideoPopupComponent,
AddTransactionPopupComponent],
AddTransactionPopupComponent,
YoutubePopupComponent],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -55,7 +55,7 @@
</p>
<br>
<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 ofbFadeInOnScroll class="action">
<i ofbicon>headset</i> <a routerLink="/sermons" class="align-top">Click here to listen online</a>

View File

@ -33,6 +33,9 @@ export class SharePopupComponent implements OnInit {
if (this.data.prefix === 'o') {
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.twitterUrl = this.twitterPartA + data.title + " - " + data.description + "&url=" + this.shareUrl;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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();
}
}

View File

@ -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%;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -0,0 +1,13 @@
.side-bar{
position: fixed;
}
.width100{
width: 100%;
}
#showAllButton{
margin-top: 10px;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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]);
}
}
}
}

View File

@ -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;
}

View File

@ -10,6 +10,9 @@ export class OfbDatePipe implements PipeTransform {
console.error("value for ofb-date-pipe was undefined or null");
return value;
}
if (typeof value === 'string') {
value = new Date(value);
}
var year = value.getFullYear();
var month = value.getMonth() + 1;
var day = value.getDate();

View File

@ -1,12 +1,42 @@
{
"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,
"title": "Sunday Evening",
"src": "",
"yt": "https://www.youtube.com/embed/wCqhj3HnhKU",
"date": "2020-05-10T19:00:00-06:00",
"archived": false,
"archived": true,
"message": "Please check back later for the Sunday Evening Service"
},