Update to Angular 7

Transactions
Dan 2019-01-21 14:10:13 -07:00
commit 438fef27a0
53 changed files with 13592 additions and 0 deletions

View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

44
Client/.gitignore vendored 100644
View File

@ -0,0 +1,44 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

27
Client/README.md 100644
View File

@ -0,0 +1,27 @@
# Ofbclient
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.2.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

136
Client/angular.json 100644
View File

@ -0,0 +1,136 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ofbclient": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "../Server/www",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets",
"src/web-app-files"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ofbclient:build"
},
"configurations": {
"production": {
"browserTarget": "ofbclient:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ofbclient:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"ofbclient-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ofbclient:serve"
},
"configurations": {
"production": {
"devServerTarget": "ofbclient:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "ofbclient"
}

View File

@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to ofbclient!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
}));
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getTitleText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

10598
Client/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
{
"name": "ofbclient",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^7.2.1",
"@angular/cdk": "^7.2.1",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
"@angular/core": "~7.2.0",
"@angular/forms": "~7.2.0",
"@angular/material": "^7.2.1",
"@angular/platform-browser": "~7.2.0",
"@angular/platform-browser-dynamic": "~7.2.0",
"@angular/router": "~7.2.0",
"core-js": "^2.5.4",
"hammerjs": "^2.0.8",
"rxjs": "~6.3.3",
"tslib": "^1.9.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.12.0",
"@angular/cli": "~7.2.2",
"@angular/compiler-cli": "~7.2.0",
"@angular/language-service": "~7.2.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~3.1.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~3.2.2"
}
}

View File

@ -0,0 +1,66 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
//Components
import { HomeComponent } from './components/home/home.component';
import { WhoWeAreComponent } from './components/whoweare/whoweare.component';
import { ServicesComponent } from './components/services/services.component';
import { SermonsComponent } from './components/sermons/sermons.component';
import { LocationComponent } from './components/location/location.component';
import { EventsPageComponent } from './components/events-page/events-page.component';
import { ContactPageComponent } from './components/contact-page/contact-page.component';
import { SalvationPageComponent } from './components/salvation-page/salvation-page.component';
const routes =
[
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
},{
path: 'home',
component: HomeComponent
},
{
path: 'whoweare',
component: WhoWeAreComponent
},
{
path: 'services',
component: ServicesComponent
},
{
path: 'contact',
component: ContactPageComponent
},
{
path: 'sermons',
component: SermonsComponent
},
{
path: 'sermons/:id',
component: SermonsComponent
},
{
path: 'location',
component: LocationComponent
},
{
path: 'events',
component: EventsPageComponent
},
{
path: 'events/:id',
component: EventsPageComponent
},
{
path: 'salvation',
component: SalvationPageComponent
}
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,121 @@
import { EventService } from './services/event.service';
import { GoogleAnalyticsService } from './services/google-analytics.service';
import { WindowRefService } from './services/window-ref.service';
import { EmailService } from './services/email.service';
import { SermonService } from './services/sermon.service';
import { LoginService } from './services/login.service';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { MatButtonModule,
MatInputModule,
MatSliderModule,
MatSnackBarModule,
MatDialogModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import 'hammerjs';
//Components
import { AppComponent } from './components/app/app.component';
import { MenuComponent } from './components/menu/menu.component';
import { IconButtonComponent } from './components/icon-button/icon-button.component';
import { HomeComponent } from './components/home/home.component';
import { WhoWeAreComponent } from './components/whoweare/whoweare.component';
import { ServicesComponent } from './components/services/services.component';
import { SecondaryPageComponent } from './components/secondary-page/secondary-page.component';
import { RecentSermonsComponent } from './components/recent-sermons/recent-sermons.component';
import { SermonSmallComponent } from './components/sermon-small/sermon-small.component';
import { DateComponent } from './components/date/date.component';
import { UpcomingEventsComponent } from './components/upcoming-events/upcoming-events.component';
import { EventComponent } from './components/event/event.component';
import { SermonsComponent } from './components/sermons/sermons.component';
import { AudioPlayerComponent } from './components/audio-player/audio-player.component';
import { LocationComponent } from './components/location/location.component';
import { SermonLargeComponent } from './components/sermon-large/sermon-large.component';
import { AddSermonPopupComponent } from './components/popups/add-sermon-popup/add-sermon-popup.component';
import { LoginPopupComponent } from './components/popups/login-popup/login-popup.component';
import { InputPopupComponent } from './components/popups/input-popup/input-popup.component';
import { EventsPageComponent } from './components/events-page/events-page.component';
import { AddEventPopupComponent } from './components/popups/add-event-popup/add-event-popup.component';
import { OkPopupComponent } from './components/popups/ok-popup/ok-popup.component';
import { YesNoPopupComponent } from './components/popups/yes-no-popup/yes-no-popup.component';
import { UpdateSermonPopupComponent } from './components/popups/update-sermon-popup/update-sermon-popup.component';
import { ContactPageComponent } from './components/contact-page/contact-page.component';
import { SharePopupComponent } from './components/popups/share-popup/share-popup.component';
import { EventLargeComponent } from './components/event-large/event-large.component';
import { SalvationPageComponent } from './components/salvation-page/salvation-page.component';
//Directives
import { FadeInOnScrollDirective } from './directives/fade-in-on-scroll.directive';
import { IconDirective } from './directives/icon.directive';
//Pipes
import { DurationPipe } from './pipes/duration.pipe';
import { SafeUrlPipe } from './pipes/safe-url.pipe';
import { OfbDatePipe } from './pipes/ofb-date.pipe';
// Routing
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [
AppComponent,
MenuComponent,
IconButtonComponent,
HomeComponent,
WhoWeAreComponent,
ServicesComponent,
FadeInOnScrollDirective,
IconDirective,
SecondaryPageComponent,
RecentSermonsComponent,
SermonSmallComponent,
DateComponent,
UpcomingEventsComponent,
EventComponent,
SermonsComponent,
AudioPlayerComponent,
DurationPipe,
LocationComponent,
SermonLargeComponent,
AddSermonPopupComponent,
LoginPopupComponent,
OkPopupComponent,
InputPopupComponent,
YesNoPopupComponent,
UpdateSermonPopupComponent,
ContactPageComponent,
SharePopupComponent,
SafeUrlPipe,
EventsPageComponent,
AddEventPopupComponent,
EventLargeComponent,
OfbDatePipe,
SalvationPageComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
//Angular Material Components
MatButtonModule,
MatInputModule,
MatSliderModule,
MatSnackBarModule,
MatDialogModule,
],
providers: [LoginService,GoogleAnalyticsService,SermonService,EventService,EmailService,WindowRefService],
entryComponents: [AddSermonPopupComponent,
LoginPopupComponent,
OkPopupComponent,
InputPopupComponent,
YesNoPopupComponent,
UpdateSermonPopupComponent,
SharePopupComponent,
AddEventPopupComponent],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,140 @@
header{
position:fixed;
top:0;
left:0;
width:100%;
height: 50px;
color: white;
vertical-align: middle;
z-index: 1000;
}
#header-background{
background-color: rgb(0, 188, 212);
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.24);
}
#logo{
margin-left: -5px;
margin-right: 10px;
}
#main-menu-button{
float: right;
margin-right: 10px;
margin-top: 5px;
}
.hideOnMobile{
display:inline-block;
width: 50px;
}
.header-button, .header-button-home{
height: 100%;
font-size: 18px;
}
.content{
margin-top: 50px;
min-height: 100%;
margin-bottom: -140px;
padding-bottom: 140px;
}
footer{
background-color: rgb(50,50,50);
padding-top: 20px;
padding-bottom: 20px;
color: white;
font-size: 16px;
height: 140px;
}
#footer-content{
margin: auto;
text-align: center;
}
.footer-panel{
display: inline-block;
box-sizing: border-box;
width: calc(33% - 20px);
margin: 10px;
min-height: 100px;
}
@media(max-width: 900px){
.header-button, .hideOnMobile{
display: none;
}
}
@media(max-width: 800px){
footer{
height: 380px;
}
.footer-panel{
display: block;
width: calc(100% - 20px);
}
.content{
margin-bottom: -380px;
padding-bottom: 380px;
}
}
.copyright{
font-size: 18px;
width: auto;
height: auto;
vertical-align: top;
}
a{
color: inherit;
}
a:visited{
color: inherit;
}
.audio-player-filler{
height: 70px;
background-color: inherit;
}
.audio-player{
position: fixed;
bottom: -65px;
width: 100%;
height: 65px;
text-align: center;
background-color: rgb(100,100,100);
box-shadow: 0 -2px 5px 0 rgba(0,0,0,0.24);
background-color: rgb(100,100,100);
color: white;
transition: .5s ease-in-out bottom;
}
.audio-player-slide-up{
bottom:0;
}
audio-player-component{
width: 100%;
max-width: 1000px;
margin: auto;
display: inline-block;
}

View File

@ -0,0 +1,58 @@
<header>
<div id="header-background" [ngStyle]="{'opacity':headerOpacity}"></div>
<button mat-button class="header-button-home" routerLink="/home"><img id="logo" src="assets/images/tiny/logo.png" height="30" alt="logo"> Old Fashion Baptist</button>
<span class="hideOnMobile"></span>
<button mat-button class="header-button" routerLink="/whoweare">Who We Are</button>
<button mat-button class="header-button" routerLink="/services">Service Times</button>
<button mat-button class="header-button" routerLink="/location">Location</button>
<button mat-button class="header-button" routerLink="/sermons">Sermons</button>
<button mat-icon-button id="main-menu-button"
(click)="mainMenuClick()">
<i ofbicon class="example-icon" style="line-height:20px;">menu</i>
</button>
</header>
<div class="content">
<router-outlet></router-outlet>
</div>
<footer>
<div id="footer-content">
<div id="footer-contact-content" class="footer-panel">
<a target="_blank" href="https://www.google.com/maps/place/Old+Fashion+Baptist+Church/@45.9814004,-112.5320574,11.87z/data=!4m12!1m6!3m5!1s0x535b078c3c74ea33:0xac299097142c5894!2sOld+Fashion+Baptist+Church!8m2!3d45.951287!4d-112.511978!3m4!1s0x535b078c3c74ea33:0xac299097142c5894!8m2!3d45.951287!4d-112.511978">
<p>5003 Wynne Ave</p>
<p>Butte, MT 59701</p>
</a>
<p><a href="tel:+1-406-494-5028" class="phone">(406) 494 - 5028</a></p>
<p>Pastor Ron Derksen</p>
<p>Associate Pastor Derek Loewen</p>
</div>
<div id="footer-services-content" class="footer-panel">
<p>Sunday School: 10AM</p>
<p>Sunday Worship: 11AM</p>
<p>Sunday Evening: 7PM</p>
<p>Wednesday Evening: 7PM</p>
<br>
</div>
<div id="footer-website-content" class="footer-panel">
<p><i ofbicon class="copyright">copyright</i> Copyright {{copyrightYear}}</p>
<p>Old Fashion Baptist</p>
<p><a routerLink="/home">ofbbutte.com</a></p>
<p>Powered by God</p>
<br>
</div>
</div>
<div class="audio-player-filler" [hidden]="!showAudioPlayer" ></div>
</footer>
<div class="audio-player" [class.audio-player-slide-up]="showAudioPlayer">
<audio-player-component (closed)="audioPlayerClosed()" (started)="audioPlayerStarted()"></audio-player-component>
</div>
<menu-component [menuOpen]="menuOpen" (closed)="mainMenuClick()"></menu-component>

View File

@ -0,0 +1,102 @@
import { GoogleAnalyticsService } from './../../services/google-analytics.service';
import { MatDialog, MatDialogConfig, MatSnackBar } from '@angular/material';
import { ActivatedRoute, Params } from '@angular/router';
import { EventService } from './../../services/event.service';
import { AudioPlayerService } from './../../services/audio-player.service';
import { AddEventPopupComponent } from './../popups/add-event-popup/add-event-popup.component';
import { Component, OnInit } from '@angular/core';
import { Event } from '../../interfaces/event';
import { LoginService } from '../../services/login.service';
@Component({
selector: 'app-events-page',
templateUrl: './events-page.component.html',
styleUrls: ['./events-page.component.css']
})
export class EventsPageComponent implements OnInit {
private dialogConfig: MatDialogConfig;
public loading: boolean = true;
public events: Event[];
public loggedIn: boolean = false;
public showAllButtonVisible: boolean = false;
public audioPlayerOpen: boolean = false;
constructor(private loginService: LoginService,
private eventService: EventService,
private dialog: MatDialog,
private snackbar: MatSnackBar,
private activatedRoute: ActivatedRoute,
private audioPlayerService: AudioPlayerService,
private googleAnalyticsService: GoogleAnalyticsService) {
this.loginService.isLoggedIn(true).subscribe(is => { this.loggedIn = is; });
this.loginService.onLogin().subscribe(is => { this.loggedIn = is.isLoggedIn; });
this.audioPlayerService.onAudioPlayerOpenClose.subscribe(is => this.audioPlayerOpen = is);
}
ngOnInit() {
this.activatedRoute.params
.subscribe((params: Params) =>{
let id = +params['id'];
id = Number.isNaN(id) ? -1 : id;
if (id > -1){
this.getSingleEvent(id);
this.showAllButtonVisible = true;
} else {
this.getEvents(true);
this.showAllButtonVisible = false;
}
});
}
getEvents(clearExisting: boolean): void{
this.loading = true;
this.events = this.events || [];
if (clearExisting){
this.events = [];
}
this.eventService.getEvents(1).subscribe(
eventReponse => {
this.addEvents(eventReponse.events);
this.loading = false;
},
error => console.error(error)
);
}
getSingleEvent(id: Number): void{
this.events = [];
this.loading = true;
this.eventService.getSingleEvent(id).subscribe(event => {
this.events.push(event.event);
this.loading = false;
this.googleAnalyticsService.shareEventConversion(event.event.id,event.event.title);
});
}
addEvents(events: Event[]): void{
console.log(events);
for(let i = 0; i < events.length; i++){
this.events.push(events[i]);
}
}
addEvent(){
let dialog = this.dialog.open(AddEventPopupComponent,this.dialogConfig);
dialog.afterClosed().subscribe(e => {
e = e as Event;
if (e != null){
this.events.unshift(e);
let snack = this.snackbar.open(e.title + " was added!", "Ok");
snack.onAction().subscribe(() => {
snack.dismiss();
});
setTimeout(() => { snack.dismiss(); },3000);
}
});
}
}

View File

@ -0,0 +1,173 @@
#content-wrapper{
background-color: transparent;
}
a{
color: inherit;
}
a:visited{
color: inherit;
}
img{
width: 70%;
height: auto;
}
img.full {
width: 100%;
height: auto;
}
.action{
font-weight: bold;
font-size: 20px;
margin-top: 15px;
}
.inline-block{
display: inline-block;
width: calc(60% - 10px);
}
.row{
position: relative;
vertical-align: middle;
padding-top: 10px;
background-color: white;
overflow: hidden;
}
.row-background{
width: 100%;
position: absolute;
z-index: -1000;
left: 0;
top: -10px;
}
.row-content{
width: 1000px;
margin: auto;
max-width: 100%;
overflow: hidden; /*Keep floating children inside this element */
}
.row-content-single-col{
text-align: center;
padding: 20px;
padding-top: 40px;
padding-bottom: 40px;
font-size: 1.5rem;
}
.row-content-col-left, .row-content-col-right{
display: inline-block;
width: calc(50% - 5px);
padding: 20px;
}
.row-content-col-right{
float: right;
}
.row-content-header{
font-weight: bold;
margin-bottom: 10px;
font-size: 1.25em;
}
.align-top{
vertical-align: top;
}
#background-image{
position: fixed;
top: 0px;
left: 0px;
width: 100%;
z-index: -1000;
}
@media(min-width:400px){
#background-image{
top: -15px;
}
}
@media(min-width:500px){
#background-image{
top: -30px;
}
}
@media(min-width:700px){
#background-image{
top: -45px;
}
}
@media(min-width:900px){
#background-image{
top: -60px;
}
}
@media(min-width:1100px){
#background-image{
top: -75px;
}
}
@media(min-width:1300px){
#background-image{
top: -100px;
}
}
#filler{
width: 100%;
padding-bottom: 35%;
}
.center{
text-align: center;
}
.tint{
background-color: rgb(240,240,240);
border-top: 1px solid rgb(200,200,200);
border-bottom: 1px solid rgb(220,220,220);
}
.see-through{
background-color: rgba(0,0,0,.8);
}
.see-through-light{
background-color: rgba(0,0,0,.3);
}
.verse{
font-style: italic;
margin: 10px;
}
@media(max-width: 850px){
.row-content-col-left, .row-content-col-right{
display: block;
width: 100%;
float:none;
text-align: center;
}
img.full {
width: 90%;
}
}

View File

@ -0,0 +1,143 @@
<img id="background-image" src="assets/images/home-images/tiny/sunset_b.jpg" alt="background image" width="100%">
<div id="filler">
</div>
<div id="content-wrapper">
<div class="row tint" *ngIf="showSpecial" >
<div class="row-content">
<div class="row-content-col-left">
<a href="assets/images/champ_flyer.jpg" download="champ_flyer.jpg"><img class="full" ofbFadeInOnScroll src="assets/images/home-images/tiny/champ.jpg" height="300"></a>
</div>
<div class="row-content-col-right align-top center">
<p ofbFadeInOnScroll class="row-content-header">Champ the Smiling Trick Horse</p>
<p ofbFadeInOnScroll>
Come join us for this special event happening September 5th and 6th at Old Fashion Baptist!
</p>
<br>
<p ofbFadeInOnScroll>
<b>Wednesday, September 5 @ 7 PM</b>
<br>
Bible Preaching & Bluegrass Gospel Music
</p>
<br>
<p ofbFadeInOnScroll>
<b>Thursday, September 6 @ 5:30 PM</b>
<br>
Food, Games, Music & Preaching
</p>
<br>
<p ofbFadeInOnScroll>
<a href="assets/images/champ_flyer.jpg" download="champ_flyer.jpg"><b>Click here to download the flyer</b></a>
</p>
<br>
<p ofbFadeInOnScroll class="action">
<i ofbicon>phone</i> <a href="tel:+1-406-494-5028" class="align-top">406-494-5028</a>
</p>
</div>
</div>
</div>
<div class="row">
<div class="row-content">
<div class="row-content-col-left align-top">
<p ofbFadeInOnScroll class="row-content-header">...that ye also may have fellowship with us...</p>
<p ofbFadeInOnScroll>It is exciting to gather together in the name of the Lord and
we sincerily hope that YOU will join us. Guests are always welcome at Old Fashion
Baptist and we look forward to seeing you!</p>
<p class="verse" ofbFadeInOnScroll>For where two or three are gathered together in my name, there am I in the midst of them. - Matthew 18:20</p>
<p ofbFadeInOnScroll class="action"><i ofbicon>info_outline</i> <a href="#" routerLink="/whoweare" class="align-top">Learn more about us</a></p>
</div>
<div class="row-content-col-right">
<!-- <img ofbFadeInOnScroll src="assets/images/home-images/2-church-color.png"> -->
<a routerLink="/whoweare"><img ofbFadeInOnScroll src="assets/images/home-images/tiny/family.jpg"></a>
</div>
</div>
</div>
<div class="row tint">
<div class="row-content">
<div class="row-content-single-col">
<p ofbFadeInOnScroll ofbGrowWhenCenter class="verse">O give thanks unto the Lord; for he is good: because his mercy endureth for ever. - Psalm 118:1</p>
</div>
</div>
</div>
<div class="row">
<div class="row-content">
<div class="row-content-col-right align-top">
<p ofbFadeInOnScroll class="row-content-header">Need a ride?</p>
<p ofbFadeInOnScroll>
Want to come but don't have a way to get here? We would be happy
to pick you up! Give us a call to schedule a ride on Sunday Morning.
</p>
<br>
<p ofbFadeInOnScroll class="action">
<i ofbicon>phone</i> <a href="tel:+1-406-494-5028" class="align-top">406-494-5028</a>
</p>
</div>
<div class="row-content-col-left">
<a href="tel:+1-406-494-5028"><img ofbFadeInOnScroll src="assets/images/home-images/tiny/bus2.jpg" height="200"></a>
</div>
</div>
</div>
<div class="row tint">
<div class="row-content">
<div class="row-content-single-col">
<p ofbFadeInOnScroll class="verse">The fear of the LORD is the beginning of wisdom: and the knowledge of the holy is understanding. - Proverbs 9:10</p>
</div>
</div>
</div>
<div class="row">
<div class="row-content">
<div class="row-content-col-left align-top">
<p ofbFadeInOnScroll class="row-content-header">The Holy Bible</p>
<div ofbFadeInOnScroll>
Here at Old Fashion Baptist Church we love the Word of God!
<p class="verse">
But he answered and said, <span class="red">It is written, Man shall not live by bread alone, but by every word that proceedeth out of the mouth of God.</span> - Matthew 4:4</p>
We encourage you to come and join as as we study, teach and preach Gods Word!
<br><br>
<p class="action">
<i ofbicon>directions</i> <a target="_blank" class="align-top" href="https://www.google.com/maps/place/Old+Fashion+Baptist+Church/@45.9814004,-112.5320574,11.87z/data=!4m12!1m6!3m5!1s0x535b078c3c74ea33:0xac299097142c5894!2sOld+Fashion+Baptist+Church!8m2!3d45.951287!4d-112.511978!3m4!1s0x535b078c3c74ea33:0xac299097142c5894!8m2!3d45.951287!4d-112.511978">Get Directions</a>
</p>
</div>
</div>
<div class="row-content-col-right">
<a target="_blank" class="align-top" href="https://www.google.com/maps/place/Old+Fashion+Baptist+Church/@45.9814004,-112.5320574,11.87z/data=!4m12!1m6!3m5!1s0x535b078c3c74ea33:0xac299097142c5894!2sOld+Fashion+Baptist+Church!8m2!3d45.951287!4d-112.511978!3m4!1s0x535b078c3c74ea33:0xac299097142c5894!8m2!3d45.951287!4d-112.511978"><img ofbFadeInOnScroll src="assets/images/home-images/tiny/bible.jpg"></a>
</div>
</div>
</div>
<div class="row tint">
<div class="row-content">
<div class="row-content-single-col">
<p ofbFadeInOnScroll class="verse">Not forsaking the assembling of ourselves together, as the manner of some is; but exhorting one another: and so much the more, as ye see the day approaching. - Hebrews 10:25</p>
</div>
</div>
</div>
<div class="row">
<div class="row-content">
<div class="row-content-col-right align-top">
<p ofbFadeInOnScroll class="row-content-header">Listen Online</p>
<p ofbFadeInOnScroll>
Many of our sermons are posted online. It is our prayer that these would be helpful and would encourage you in your walk with the Lord.
</p>
<br>
<p ofbFadeInOnScroll class="action">
<i ofbicon>headset</i> <a routerLink="/sermons" class="align-top">Click here to listen online</a>
</p>
</div>
<div class="row-content-col-left">
<a routerLink="/sermons"><img ofbFadeInOnScroll src="assets/images/home-images/tiny/headphones.png" height="200"></a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
import { Component, HostListener } from '@angular/core';
@Component({
selector: 'home-component',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent {
backgroundTop: string = "0px";
public get showSpecial() : boolean {
let maxDate = new Date(2018,8,6); // September 6th 2018 -- Set the month one month behind since JavaScript dates are 0 based
let now = new Date();
if (now.getFullYear() > maxDate.getFullYear()) return false;
if (now.getFullYear() == maxDate.getFullYear()) {
if (now.getMonth() > maxDate.getMonth()) return false;
if (now.getMonth() == maxDate.getMonth()) {
if (now.getDate() > maxDate.getDate()) return false;
}
}
return true;
}
@HostListener('window:scroll', ['$event'])
onScroll(event){
let scrollTop = event.target.documentElement.scrollTop || event.target.body.scrollTop || window.pageYOffset;
//this.backgroundTop = Math.min((scrollTop * .30),100) * -1 + "px";
}
}

View File

@ -0,0 +1,114 @@
import { EventService } from './../../../services/event.service';
import { OkPopupComponent } from './../ok-popup/ok-popup.component';
import { LoginPopupComponent } from './../login-popup/login-popup.component';
import { MatDialog, MatDialogRef, MatSnackBar } from '@angular/material';
import { Event } from './../../../interfaces/event';
import { LoginService } from './../../../services/login.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add-event-popup',
templateUrl: './add-event-popup.component.html',
styleUrls: ['./add-event-popup.component.css']
})
export class AddEventPopupComponent implements OnInit {
public addEventButtonText: string = "Add Event";
public addEventButtonDisabled: boolean = false;
public eventTitle: string;
public eventStartDate: Date = new Date();
private _eventEndDate: Date = new Date();
public get eventEndDate(): Date{
if (new Date(this.eventStartDate).getTime() > new Date(this._eventEndDate).getTime()){
this._eventEndDate = this.eventStartDate;
}
return this._eventEndDate;
}
public set eventEndDate(val: Date){
this._eventEndDate = val;
}
public eventDescription: string;
public errorMessages: string[] = [];
constructor(private MatDialog: MatDialog, private MatDialogRef: MatDialogRef<AddEventPopupComponent>, private loginService: LoginService, private eventService: EventService) { }
ngOnInit() {
}
onSubmit(){
this.updateAddButton(false);
//First check to see if we are logged in
this.loginService.isLoggedIn(true).subscribe(is => {
if (is === true){
this.addEvent();
} else {
let popup = this.MatDialog.open(LoginPopupComponent);
popup.afterClosed().subscribe(r => {
if (r === true){
this.addEvent();
} else {
this.updateAddButton(true);
}
});
}
},
error =>{
this.updateAddButton(true);
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Error',message:'There was an error adding the event\n' + error}});
});
}
updateAddButton(enable: boolean){
if (enable){
this.addEventButtonText = "Add Event";
} else {
this.addEventButtonText = "Please Wait...";
}
this.addEventButtonDisabled = !enable;
}
private addEvent(): void {
//Check fields
this.errorMessages = [];
if (this.eventTitle == null || this.eventTitle == ""){
this.errorMessages.push("Please enter a title");
}
if (this.eventStartDate == null){
this.errorMessages.push("Please enter a event start date");
}
if (this.eventEndDate == null){
this.errorMessages.push("Please enter a event end date");
}
if (this.eventDescription == null || this.eventDescription == ""){
this.errorMessages.push("Please enter a description");
}
if (this.errorMessages.length > 0){ this.updateAddButton(true); return; }
let e = new Event();
e.title = this.eventTitle;
e.startDate = this.eventStartDate;
e.endDate = this.eventEndDate;
e.description = this.eventDescription;
this.eventService.addEvent(e).subscribe(
data=>{
this.updateAddButton(true);
this.MatDialogRef.close(data['event']);
},
error => {
this.updateAddButton(true);
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Upload Error',message:'There was an error uploading the sermon\n' + error}});
});
}
cancel(evt){
evt.preventDefault();
this.MatDialogRef.close();
}
}

View File

@ -0,0 +1,146 @@
import { OkPopupComponent } from './../ok-popup/ok-popup.component';
import { Observable, Subscription } from 'rxjs';
import { LoginPopupComponent } from './../login-popup/login-popup.component';
import { MatDialog, MatDialogRef, MatSnackBar } from '@angular/material';
import { Sermon } from './../../../interfaces/sermon';
import { SermonService } from './../../../services/sermon.service';
import { LoginService } from './../../../services/login.service';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpEvent, HttpEventType } from '@angular/common/http';
@Component({
selector: 'app-add-sermon-popup',
templateUrl: './add-sermon-popup.component.html',
styleUrls: ['./add-sermon-popup.component.css']
})
export class AddSermonPopupComponent implements OnInit {
public addSermonButtonText: string = "Add Sermon";
public addSermonButtonDisabled: boolean = false;
public sermonTitle: string;
public sermonDate: Date = new Date();
public sermonSpeaker: string;
public sermonDescription: string;
public sermonFile: File;
public errorMessages: string[] = [];
public uploadTotal: number = 100;
public uploadProgress: number = 0;
public monitorProgress: boolean;
private progressSubscription: Subscription;
constructor(private MatDialog: MatDialog, private MatDialogRef: MatDialogRef<AddSermonPopupComponent>, private loginService: LoginService, private sermonService: SermonService) { }
ngOnInit() {
}
onSubmit(){
this.updateAddButton(false);
//First check to see if we are logged in
this.loginService.isLoggedIn(true).subscribe(is => {
if (is === true){
this.addSermon();
} else {
let popup = this.MatDialog.open(LoginPopupComponent);
popup.afterClosed().subscribe(r => {
if (r === true){
this.addSermon();
} else {
this.updateAddButton(true);
}
});
}
},
error =>{
this.updateAddButton(true);
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Upload Error',message:'There was an error uploading the sermon\n' + error}});
});
}
updateAddButton(enable: boolean){
if (enable){
this.addSermonButtonText = "Add Sermon";
} else {
this.addSermonButtonText = "Please Wait...";
}
this.addSermonButtonDisabled = !enable;
}
private addSermon(): void {
//Check fields
this.errorMessages = [];
if (this.sermonTitle == null || this.sermonTitle == ""){
this.errorMessages.push("Please enter a title");
}
if (this.sermonDate == null){
this.errorMessages.push("Please enter a sermon date");
}
if (this.sermonSpeaker == null || this.sermonSpeaker == ""){
this.errorMessages.push("Please enter a speaker");
}
if (this.sermonDescription == null || this.sermonDescription == ""){
this.errorMessages.push("Please enter a description");
}
if (this.sermonFile == null){
this.errorMessages.push("Please add a sermon MP3 file");
}
if (this.sermonFile != null && this.sermonFile.type != 'audio/mp3'){
this.errorMessages.push("File must be a MP3");
}
if (this.errorMessages.length > 0){ this.updateAddButton(true); return; }
let s = new Sermon();
s.title = this.sermonTitle;
s.author = this.sermonSpeaker;
s.description = this.sermonDescription;
s.date = this.sermonDate;
//Start monitoring Progress
this.monitorProgress = true;
this.sermonService.addSermon(s,this.sermonFile).subscribe(
data => {this.getEventMessage(data); },
error => {
alert(error);
this.updateAddButton(true);
this.monitorProgress = false;
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Upload Error',message:'There was an error uploading the sermon\n' + error}});
});
}
private getEventMessage(event: HttpEvent<any>) {
switch (event.type) {
case HttpEventType.Sent:
return;
case HttpEventType.UploadProgress:
this.uploadProgress = event.loaded;
this.uploadTotal = event.total;
return;
case HttpEventType.Response:
this.updateAddButton(true);
this.monitorProgress = false;
this.MatDialogRef.close(event['body']['sermon']);
return;
default:
return;
}
}
onFileChange(event){
this.sermonFile = event.srcElement.files[0];
}
cancel(evt){
evt.preventDefault();
this.MatDialogRef.close();
}
}

View File

@ -0,0 +1,57 @@
import { OkPopupComponent } from './../ok-popup/ok-popup.component';
import { MatSnackBar, MatDialogRef, MatDialog, MatDialogConfig } from '@angular/material';
import { LoginService } from './../../../services/login.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login-popup',
templateUrl: './login-popup.component.html',
styleUrls: ['./login-popup.component.css']
})
export class LoginPopupComponent implements OnInit {
public loginButtonText: string = 'Login';
public loginButtonDisabled: boolean = false;
public username: string;
public password: string;
constructor(private MatDialog: MatDialog, private MatDialogRef: MatDialogRef<LoginPopupComponent>, private loginService: LoginService, private snackbar: MatSnackBar) { }
ngOnInit() {
}
onSubmit(){
this.loginButtonText = 'Please Wait...';
this.loginButtonDisabled = true;
this.loginService.login(this.username,this.password).subscribe(
(data:any) => {
if (data.message === 'Logged In'){
this.MatDialogRef.close(true);
let s = this.snackbar.open("Logged In","OK");
s.onAction().subscribe(()=>{ s.dismiss(); });
s.afterOpened().subscribe(()=>{ setTimeout(()=>{ s.dismiss(); },3000); });
} else {
this.showLoginError(data.message);
}
this.loginButtonText = 'Login';
this.loginButtonDisabled = false;
},
error => {
console.error(error);
this.showLoginError(error);
this.loginButtonText = 'Login';
this.loginButtonDisabled = false;
});
}
showLoginError(message: string){
let opts = new MatDialogConfig;
opts.data = { title:'Login Error','message':message };
let popup = this.MatDialog.open(OkPopupComponent,opts);
}
cancel(){
this.MatDialogRef.close();
}
}

View File

@ -0,0 +1,97 @@
import { LoginPopupComponent } from './../login-popup/login-popup.component';
import { OkPopupComponent } from './../ok-popup/ok-popup.component';
import { Sermon } from './../../../interfaces/sermon';
import { SermonService } from './../../../services/sermon.service';
import { LoginService } from './../../../services/login.service';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { Component, OnInit, Inject } from '@angular/core';
@Component({
selector: 'app-update-sermon-popup',
templateUrl: './update-sermon-popup.component.html',
styleUrls: ['./update-sermon-popup.component.css']
})
export class UpdateSermonPopupComponent implements OnInit {
public updateSermonButtonText: string = "Update Sermon";
public updateSermonButtonDisabled: boolean = false;
public sermonId: number;
public sermonTitle: string;
public sermonDate: Date = new Date();
public sermonSpeaker: string;
public sermonDescription: string;
constructor(@Inject(MAT_DIALOG_DATA) private data: any,
private MatDialog: MatDialog,
private MatDialogRef: MatDialogRef<UpdateSermonPopupComponent>,
private loginService: LoginService,
private sermonService: SermonService) {
this.sermonId = this.data.id;
this.sermonTitle = this.data.title;
this.sermonDate = this.data.date;
this.sermonSpeaker = this.data.author;
this.sermonDescription = this.data.description;
}
ngOnInit() {
}
onSubmit(){
this.updateButton(false);
//First check to see if we are logged in
this.loginService.isLoggedIn(true).subscribe(is => {
if (is === true){
this.updateSermon();
} else {
let popup = this.MatDialog.open(LoginPopupComponent);
popup.afterClosed().subscribe(r => {
if (r === true){
this.updateSermon();
} else {
this.updateButton(true);
}
});
}
},
error =>{
this.updateButton(true);
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Upload Error',message:'There was an error uploading the sermon\n' + error}});
});
}
private updateSermon(): void {
let s = new Sermon;
s.id = this.sermonId;
s.title = this.sermonTitle;
s.author = this.sermonSpeaker;
s.description = this.sermonDescription;
s.date = this.sermonDate;
//Start monitoring Progress
this.sermonService.updateSermon(s).subscribe(
(data:any)=>{
this.MatDialogRef.close(data.sermon);
},
error => {
this.updateButton(true);
let errorDialog = this.MatDialog.open(OkPopupComponent,{data:{title:'Update Error',message:'There was an error updating the sermon\n' + error}});
});
}
updateButton(enable: boolean){
if (enable){
this.updateSermonButtonText = "Update Sermon";
} else {
this.updateSermonButtonText = "Please Wait...";
}
this.updateSermonButtonDisabled = !enable;
}
cancel(){
this.MatDialogRef.close();
}
}

View File

@ -0,0 +1,15 @@
<div class="sermon-container">
<p class="sermon-header" >Recent Sermons</p>
<p *ngIf="loading">Loading...</p>
<ul class="sermons">
<li *ngFor="let sermon of sermons; let i = index;">
<sermon-small-component
[title]="sermon.title"
[date]="sermon.date"
[delayFadeIn]="(i+1)*200"
[url]="sermon.file"
[id]="sermon.id"
></sermon-small-component>
</li>
</ul>
</div>

View File

@ -0,0 +1,34 @@
import { Component, OnInit, Input } from '@angular/core';
import { Sermon } from '../../interfaces/sermon';
import { SermonService } from '../../services/sermon.service';
@Component({
selector: 'recent-sermons-component',
templateUrl: './recent-sermons.component.html',
styleUrls: ['./recent-sermons.component.css']
})
export class RecentSermonsComponent implements OnInit {
sermons: Sermon[];
loading: boolean = false;
@Input()
numberOfSermonsToShow = 3;
constructor(private sermonService: SermonService){
}
ngOnInit(): void {
this.getSermons();
}
getSermons(): void{
this.sermons = [];
this.loading = true;
this.sermonService.getSermons(this.numberOfSermonsToShow).subscribe(response => {
this.sermons = response.sermons;
this.loading = false;
console.log(response);
});
}
}

View File

@ -0,0 +1,59 @@
import { Component, AfterContentInit, Input } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { AudioPlayerService } from '../../services/audio-player.service';
@Component({
selector: 'sermon-small-component',
templateUrl: './sermon-small.component.html',
styleUrls: ['./sermon-small.component.css'],
animations:[
trigger("inout",[
state("1", style({ opacity: '1', transform:'translateX(0)' })),
state("0",style({ opacity: '0', transform:'translateX(5%)' })),
transition('* => 1',[
animate('500ms ease-in-out')
]),
transition(':enter',[
animate('0ms')
])
])
]
})
export class SermonSmallComponent implements AfterContentInit {
@Input()
id: number;
@Input()
title: string;
@Input()
description: string;
@Input()
date: Date;
@Input()
author: string;
@Input()
url: string;
public startFadeIn: boolean = false;
@Input()
public delayFadeIn: number = 100;
constructor(private audioPlayer: AudioPlayerService){
}
play(): void{
if (this.audioPlayer.getIsPlaying() == true && this.audioPlayer.getMetaData() != null && this.audioPlayer.getMetaData().id == this.id){
this.audioPlayer.pause();
} else {
if (this.audioPlayer.getMetaData() != null && this.audioPlayer.getMetaData().id == this.id){
this.audioPlayer.resume();
} else {
this.audioPlayer.play(this.url,{id:this.id,title:this.title,author:this.author});
}
}
}
ngAfterContentInit(): void{
setTimeout(() => this.startFadeIn = true, this.delayFadeIn);
}
}

View File

@ -0,0 +1,41 @@
<secondary-page-component [hideSideBarOnMobile]="true" >
<div mainContent>
<div class="mobile-search">
<mat-form-field class="width100">
<input #searchMobile matInput placeholder="Search Sermons" [value]="searchTerm" (keyup)="searchChange(searchMobile.value)">
</mat-form-field>
<br><br>
</div>
<ul>
<li *ngFor="let sermon of sermons; let i = index;">
<sermon-large-component
[id]="sermon.id"
[title]="sermon.title"
[date]="sermon.date"
[description]="sermon.description"
[author]="sermon.author"
[delayFadeIn]="(i-((((sermons.length*10)/10)-10) > 0 ? (((sermons.length*10)/10)-10) : 0))*200"
[url]="sermon.file"
[loggedIn]="loggedIn"
></sermon-large-component>
</li>
<button mat-button [disabled]="loading" class="width100" style="text-align: center;" (click)="loadMoreSermonsClick()">
<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>
</ul>
<div class="fab-buttons" >
<button mat-fab (click)="searchFabClick()"><i ofbicon>search</i></button>
<button mat-fab *ngIf="loggedIn" (click)="addSermon()"><i ofbicon>add</i></button>
<div id="audio-player-filler" *ngIf="audioPlayerOpen"></div>
</div>
</div>
<div sideBar class="side-bar">
<div class="search-bar">
<mat-form-field class="width100">
<input #search matInput placeholder="Search Sermons" [value]="searchTerm" (keyup)="searchChange(search.value)">
</mat-form-field>
</div>
<button mat-raised-button *ngIf="loggedIn" class="width100" (click)="addSermon()" >Add Sermon</button>
</div>
</secondary-page-component>

View File

@ -0,0 +1,141 @@
import { InputPopupComponent } from './../popups/input-popup/input-popup.component';
import { AudioPlayerService } from './../../services/audio-player.service';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { SermonService } from '../../services/sermon.service';
import { LoginService } from '../../services/login.service';
import { Sermon } from '../../interfaces/sermon';
import { MatSnackBar, MatDialog, MatDialogConfig } from '@angular/material';
import { AddSermonPopupComponent } from '../popups/add-sermon-popup/add-sermon-popup.component';
@Component({
selector: 'sermons-component',
templateUrl: './sermons.component.html',
styleUrls: ['./sermons.component.css'],
})
export class SermonsComponent implements OnInit {
public sermons: Sermon[];
public loading: boolean = false;
public page: number = 1;
public searchTerm: string = '';
public loggedIn: boolean = false;
public audioPlayerOpen: boolean;
private searchWait: any;
private dialogConfig: MatDialogConfig;
constructor(private loginService: LoginService,
private snackbar: MatSnackBar,
private dialog: MatDialog,
private sermonService: SermonService,
private route: ActivatedRoute,
private location: Location,
private audioPlayerService: AudioPlayerService){
this.loginService.isLoggedIn(true).subscribe(is => { this.loggedIn = is; });
this.loginService.onLogin().subscribe(is => { this.loggedIn = is.isLoggedIn; });
this.audioPlayerService.onAudioPlayerOpenClose.subscribe(is => { this.audioPlayerOpen = is; });
}
ngOnInit(): void{
this.route.params
// (+) converts string 'id' to a number
.subscribe((params: Params) => {
let id = +params['id'];
id = Number.isNaN(id) ? -1 : id;
if (id > -1){
this.page = 0;
this.getSingleSermon(id);
} else {
this.getSermons(true);
}
});
}
searchChange(searchTerm): void{
this.searchTerm = searchTerm;
if (this.searchWait) clearTimeout(this.searchWait);
this.searchWait = setTimeout(()=>this.getSermons(true),400);
}
loadMoreSermonsClick(): void{
this.page++;
//this.location.go("/sermons/" + this.page);
this.getSermons(false);
}
getSingleSermon(id: number){
this.loading = true;
this.sermons = this.sermons || [];
this.sermons = [];
this.sermonService.getSermonById(id).subscribe(
response => {
this.sermons.push(response.sermon)
this.loading = false;
},
error => {
this.getSermons(true);
} );
}
getSermons(clearExisting): void{
this.loading = true;
this.sermons = this.sermons || [];
if (clearExisting) {
this.sermons = [];
this.page = 1;
}
this.sermonService.searchSermons(this.searchTerm,this.page).subscribe(
response => {
this.addSermons(response.sermons);
this.loading = false;
},
error => console.error(error) );
}
addSermons(sermons: Sermon[]): void{
for(let i = 0; i < sermons.length; i++){
this.sermons.push(sermons[i]);
}
}
addSermon(){
let dialog = this.dialog.open(AddSermonPopupComponent,this.dialogConfig);
dialog.afterClosed().subscribe(s => {
s = s as Sermon;
if (s){
this.sermons.unshift(s);
let snack = this.snackbar.open(s.title + " was added!", "Ok");
snack.onAction().subscribe(()=>{
snack.dismiss();
});
setTimeout(()=>{snack.dismiss();},3000);
}
});
}
searchFabClick(){
let opts = new MatDialogConfig;
opts.data = { title:'Search','message':'' };
let dialog = this.dialog.open(InputPopupComponent,opts);
dialog.afterClosed().subscribe(search => {
if (typeof search !== 'undefined' && this.searchTerm != search){
this.searchTerm = search;
this.searchChange(this.searchTerm);
window.scrollTo(0, 0);
}
});
}
}

View File

@ -0,0 +1,37 @@
import { Component, OnInit, Input } from '@angular/core';
import { Event } from '../../interfaces/event';
import { EventService } from '../../services/event.service';
@Component({
selector: 'upcoming-events-component',
templateUrl: './upcoming-events.component.html',
styleUrls: ['./upcoming-events.component.css']
})
export class UpcomingEventsComponent implements OnInit {
public events: Event[];
loading: boolean = false;
@Input()
numberOfEventsToShow: number = 3;
@Input()
hideWhenNoEvents: boolean = true;
constructor(private eventService: EventService){
}
ngOnInit(): void {
this.getEvents();
}
getEvents(): void{
this.events = [];
this.loading = true;
this.eventService.getEvents(1).subscribe(events => {
this.events = events.events.slice(0,this.numberOfEventsToShow);
this.loading = false;
});
}
}

View File

@ -0,0 +1,18 @@
import { environment } from '../../environments/environment';
export const EVENTS_ADD_URL = environment.baseUrl + "/api2/events/a/";
export const EVENT_BY_ID = environment.baseUrl + "/api2/events/";
export const EVENTS_BY_PAGE_URL = environment.baseUrl + "/api2/events/page/";
export const EVENTS_DELETE_BY_ID_URL = environment.baseUrl + "/api2/events/a/";
export const SERMONS_BY_ID = environment.baseUrl + '/api2/sermons/';
export const SERMONS_BY_PAGE_URL = environment.baseUrl + '/api2/sermons/page/';
export const SERMONS_BY_SEARCH_URL = environment.baseUrl + '/api2/sermons/search';
export const SERMON_MP3_BASE_URL = '//ofbbutte.com/static/media/';
export const SERMON_ADD_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_DELETE_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_UPDATE_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_DOWNLOAD_URL = environment.baseUrl + "/api2/sermons/download/";
export const LOGIN_URL = environment.baseUrl + '/api2/login';
export const LOGIN_VALIDATE_TOKEN = '';
export const EMAIL_URL = environment.baseUrl + "/api2/email";
export const RANDOM_VERSE_URL = "//www.kingjamesbibleonline.org/popular-bible-verses-widget.php";

View File

@ -0,0 +1,18 @@
export class Event{
id: number;
title: string;
startDate: Date;
endDate: Date;
description: string;
}
export class EventResponse {
status: number;
events: Event[];
}
export class SingleEventResponse {
status: number;
event: Event;
}

View File

@ -0,0 +1,19 @@
export class Sermon{
id: number;
title: string;
date: Date;
uploadDate: Date;
description: string;
author: string;
url: string;
}
export class SingleSermonResponse {
status: number;
sermon: Sermon;
}
export class MultipleSermonResponse {
status: number;
sermons: Sermon[];
}

View File

@ -0,0 +1,131 @@
import { Injectable } from '@angular/core';
import { Sermon } from '../interfaces/sermon';
import { AudioStates } from '../enum/audio-states';
import { BehaviorSubject } from 'rxjs';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { SERMON_MP3_BASE_URL } from '../constants/urls';
@Injectable()
export class AudioPlayerService {
private audio: any = null;
private metaData: any = null;
public loading: boolean = true;
private stateChanged: Subject<{metaData:any,state:AudioStates,error:any}>;
public onAudioPlayerOpenClose: BehaviorSubject<boolean>;
constructor(){
this.stateChanged = new Subject<{metaData:any,state:AudioStates,error:any}>();
this.onAudioPlayerOpenClose = new BehaviorSubject<boolean>(false);
this.audio = new Audio();
this.audio.addEventListener("loadeddata",()=>this.doPlay());
this.audio.addEventListener("error",()=>this.error());
this.audio.addEventListener("ended",()=>this.ended());
}
updateAudioPlayerOpen(isOpen: boolean){
this.onAudioPlayerOpenClose.next(isOpen);
}
play(src: string, metaData?: any): void{
src = SERMON_MP3_BASE_URL + src;
try{
if (typeof src === 'undefined' || src == null) return;
this.loading = true;
this.metaData = metaData;
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Loading,error:null});
if (this.audio == null){
this.audio = new Audio(src);
} else {
this.audio.pause();
this.audio.src = src;
this.audio.load();
}
} catch(e){
console.error(e);
}
}
private doPlay(){
this.loading = false;
try{
if (this.audio.readyState >= 3){
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Playing,error:null});
this.audio.play();
}
} catch(e){
console.error(e);
}
}
private error(){
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Error,error:this.audio.error});
this.audio = null;
this.metaData = null;
this.loading = false;
}
private ended(){
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Paused,error:null});
}
resume(): void{
if (this.audio == null) {
console.error("nothing to resume");
return;
}
this.audio.play();
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Playing,error:null});
}
pause(): void{
if (this.audio == null) return;
this.audio.pause();
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Paused,error:null});
}
stop(): void{
if (this.audio == null) return;
this.audio.pause();
this.stateChanged.next({metaData:this.metaData,state:AudioStates.Paused,error:null});
this.metaData = null;
}
cleanup(): void{
this.audio = null;
}
setPosition(position: number): void{
if (this.audio == null) return;
this.audio.currentTime = position;
}
getIsPlaying(): boolean{
if (this.audio == null) return false;
return !this.audio.paused;
}
getCurrentTime(): number{
if (this.audio == null) return null;
return this.audio.currentTime;
}
getDuration(): number{
if (this.audio == null) return null;
return this.audio.duration;
}
getMetaData(): any{
return this.metaData;
}
onStateChanged(): Observable<{metaData:any,state:AudioStates,error:any}>{
return this.stateChanged.asObservable();
}
}

View File

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { RANDOM_VERSE_URL } from '../constants/urls';
@Injectable()
export class BibleVerseService {
constructor(private httpClient: HttpClient){}
randomVerse(){
//return this.http.get(RANDOM_VERSE_URL).map(this.gotData).catch(this.dataError);
}
gotData(res: Response){
console.log(res);
}
}

View File

@ -0,0 +1,50 @@
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { EMAIL_URL } from '../constants/urls';
@Injectable()
export class EmailService {
constructor(private httpClient: HttpClient){
}
sendEmail(fromName: string,
fromEmail: string,
fromPhone: string,
message: string,
hp: string){
let body = {
name: fromName,
email: fromEmail,
phone: fromPhone,
message: message,
hp: hp
};
return this.httpClient.post(EMAIL_URL, body, {withCredentials:true})
.pipe(catchError(this.handleError))
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
}

View File

@ -0,0 +1,97 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap, map } from 'rxjs/operators';
import { Event, EventResponse, SingleEventResponse } from '../interfaces/event';
import { EVENTS_BY_PAGE_URL,
EVENT_BY_ID,
EVENTS_ADD_URL,
EVENTS_DELETE_BY_ID_URL } from '../constants/urls';
import { getBodyNode } from '@angular/animations/browser/src/render/shared';
@Injectable()
export class EventService {
constructor(private httpClient: HttpClient){
}
getEvents(page: number): Observable<EventResponse> {
let url = EVENTS_BY_PAGE_URL + page + "?pageSize=" + 10;
return this.httpClient.get<EventResponse>(url)
.pipe(tap(e => this.setMultipleEventDates(e)),
catchError(this.handleError));
};
getSingleEvent(id: Number): Observable<SingleEventResponse>{
let url = EVENT_BY_ID + id;
return this.httpClient.get<SingleEventResponse>(url).pipe(tap(e => this.setSingleEventDates(e)), catchError(this.handleError));
};
setEventDates(event: Event): Event {
event.startDate = new Date(event.startDate);
event.endDate = new Date(event.endDate);
return event;
}
setSingleEventDates(event : SingleEventResponse) : SingleEventResponse {
event.event = this.setEventDates(event.event);
return event;
}
setMultipleEventDates(event: EventResponse) : EventResponse {
event.events.forEach(e => {
e = this.setEventDates(e);
});
return event;
}
addEvent(event: Event){
let fd = new FormData();
fd.append("title",event.title);
fd.append("description",event.description);
fd.append("startDate",new Date(event.startDate).getTime().toString()); //Pass date as milliseconds since 1970
fd.append("endDate",new Date(event.endDate).getTime().toString()); //Pass date as milliseconds since 1970
fd.append("timezoneOffset",new Date().getTimezoneOffset().toString());
return this.httpClient.post(EVENTS_ADD_URL, fd, {withCredentials:true}).pipe(catchError(this.handleError));
}
deleteEvent(eventId: number){
let ro = {
body: {"id":eventId},
withCredentials: true
};
return this.httpClient.delete(EVENTS_DELETE_BY_ID_URL, ro).pipe(catchError(this.handleError));
}
toEvent(json: any): Event{
return {
id: json.id,
title: json.title,
startDate: new Date(json.startDate),
endDate: new Date(json.endDate),
description: json.description
}
}
private handleError(error: HttpErrorResponse) {
console.error(error);
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
}

View File

@ -0,0 +1,68 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, Subject, throwError, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { LOGIN_URL } from '../constants/urls';
@Injectable()
export class LoginService {
private loggedIn: boolean = false;
private onChange: Subject<{isLoggedIn: boolean}>;
private lastLoginCheck: Date;
private options: {};
constructor(private httpClient: HttpClient){
this.options = {
withCredentials: true
};
this.lastLoginCheck = new Date();
this.lastLoginCheck.setHours(this.lastLoginCheck.getHours() - 2);
this.onChange = new Subject<{isLoggedIn: boolean}>();
}
login(username: string, password: string){
return this.httpClient.post(LOGIN_URL, {userName: username, password: password}, this.options).pipe(catchError(this.handleError));
}
isLoggedIn(fromServer: boolean = false){
var now = new Date();
var hours = Math.abs(now.valueOf() - this.lastLoginCheck.valueOf()) / 36e5;
if (hours > 0.75 || fromServer === true){
return this.httpClient.post(LOGIN_URL + "/isloggedin",{},this.options)
.pipe(map((d:any) => {
let is = d.loggedIn === true;
this.loggedIn = is;
this.lastLoginCheck = new Date();
this.onChange.next({isLoggedIn: this.loggedIn});
return this.loggedIn;
}),
catchError(this.handleError));
} else {
let val = this.loggedIn;
return of(val);
}
}
onLogin(): Observable<{isLoggedIn: boolean}>{
return this.onChange.asObservable();
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
}

View File

@ -0,0 +1,119 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpRequest } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Sermon, MultipleSermonResponse, SingleSermonResponse } from '../interfaces/sermon';
import { SERMONS_BY_ID,
SERMONS_BY_PAGE_URL,
SERMON_MP3_BASE_URL,
SERMONS_BY_SEARCH_URL,
SERMON_ADD_URL,
SERMON_DELETE_URL,
SERMON_UPDATE_URL } from '../constants/urls';
@Injectable()
export class SermonService {
private pageSize: number = 10;
constructor(private httpClient: HttpClient){
}
getSermons(count: number): Observable<MultipleSermonResponse> {
let url = SERMONS_BY_PAGE_URL + 1 + "?pageSize=" + 5;
return this.httpClient.get<MultipleSermonResponse>(url).pipe(tap(e => this.setMultipleDates(e)), catchError(this.handleError));
};
getSermonsByPage(page: number): Observable<MultipleSermonResponse>{
let url = SERMONS_BY_PAGE_URL + page + "?pageSize=" + this.pageSize;
return this.httpClient.get<MultipleSermonResponse>(url).pipe(tap(e => this.setMultipleDates(e)), catchError(this.handleError));
}
getSermonById(id: number): Observable<SingleSermonResponse>{
let url = SERMONS_BY_ID + id + "?pageSize=" + this.pageSize;
return this.httpClient.get<SingleSermonResponse>(url).pipe(tap(e => this.setSingleDate(e)), catchError(this.handleError));
}
searchSermons(searchTerm: string, page: number): Observable<MultipleSermonResponse>{
let url = SERMONS_BY_SEARCH_URL + "?searchTerm=" + searchTerm + "&page=" + page + "&pageSize=" + this.pageSize;
return this.httpClient.get<MultipleSermonResponse>(url).pipe(tap(e => this.setMultipleDates(e)), catchError(this.handleError));
}
addSermon(sermon: Sermon, sermonFile: File){
let fd = new FormData();
fd.append("title",sermon.title);
fd.append("description",sermon.description);
fd.append("date",new Date(sermon.date).getTime().toString()); //Pas date as milliseconds since 1970
fd.append("author",sermon.author);
fd.append("file",sermonFile);
fd.append("timezoneOffset",new Date().getTimezoneOffset().toString());
const req = new HttpRequest('POST', SERMON_ADD_URL, fd, {
reportProgress: true,
withCredentials: true
});
return this.httpClient.request(req).pipe(
catchError(this.handleError)
);
}
updateSermon(sermon: Sermon){
let fd = new FormData();
fd.append("id",sermon.id.toString());
fd.append("title",sermon.title);
fd.append("description",sermon.description);
fd.append("date",new Date(sermon.date).toUTCString());
fd.append("author",sermon.author);
fd.append("timezoneOffset",new Date().getTimezoneOffset().toString());
return this.httpClient.put<Sermon>(SERMON_UPDATE_URL,fd,{withCredentials:true})
.pipe(catchError(this.handleError));
}
deleteSermon(sermonId: number){
let options = {
withCredentials: true,
body: {
"id" : sermonId
}
};
return this.httpClient.delete(SERMON_DELETE_URL,options)
.pipe(catchError(this.handleError));
}
setSingleDate(e : SingleSermonResponse): SingleSermonResponse {
e.sermon = this.setDates(e.sermon);
return e;
}
setMultipleDates(e : MultipleSermonResponse) : MultipleSermonResponse {
e.sermons.forEach(s => {
s = this.setDates(s);
});
return e;
}
setDates(sermon: Sermon): Sermon {
sermon.date = new Date(sermon.date);
sermon.uploadDate = new Date(sermon.uploadDate);
return sermon;
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,11 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11

View File

@ -0,0 +1,4 @@
export const environment = {
production: true,
baseUrl: ""
};

View File

@ -0,0 +1,17 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
baseUrl: "http://localhost:25776"
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,96 @@
<!doctype html>
<html>
<head>
<!-- V2 -->
<base href="/">
<meta charset="utf-8">
<title>Old Fashion Baptist Church</title>
<!-- Google Analytics -->
<!-- Global Site Tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-107601801-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
</script>
<!---->
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="180x180" href="/web-app-files/apple-touch-icon.png?v=LbWKNzlwGb">
<link rel="icon" type="image/png" sizes="32x32" href="/web-app-files/favicon-32x32.png?v=LbWKNzlwGb">
<link rel="icon" type="image/png" sizes="16x16" href="/web-app-files/favicon-16x16.png?v=LbWKNzlwGb">
<link rel="manifest" href="/web-app-files/manifest.json?v=LbWKNzlwGb">
<link rel="mask-icon" href="/web-app-files/safari-pinned-tab.svg?v=LbWKNzlwGb" color="#00bcd4">
<link rel="shortcut icon" href="/web-app-files/favicon.ico?v=LbWKNzlwGb">
<meta name="apple-mobile-web-app-title" content="Old Fashion Baptist">
<meta name="application-name" content="Old Fashion Baptist">
<meta name="msapplication-config" content="/web-app-files/browserconfig.xml?v=LbWKNzlwGb">
<meta name="theme-color" content="#00bcd4">
<!-- End Favicons -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.loader-background{
background-color: rgb(0, 188, 212);
position: fixed;
left:0px;
top:0px;
height: 100%;
width: 100%;
text-align: center;
overflow-y: auto;
}
.loader {
color: #ffffff;
margin: 40px auto;
position: relative;
font-size: 24px;
}
.loader-small{
color: #ffffff;
margin: 70px auto;
position: relative;
font-size: 18px;
}
.address{
font-size: 16px;
}
.service-times{
color: #ffffff;
font-size: 16px;
margin: 5px auto;
}
.ie{
padding: 15px;
background-color: red;
color: white;
font-size: 24px;
}
.ie-small{
font-size: 16px;
}
</style>
</head>
<body>
<app-root>
<div class="loader-background">
<!--[if IE]>
<div class="ie">Oh No! Looks like your browser is pretty outdated so our site may not work correctly.
<p class="ie-small">Please consider updating</p>
</div>
<![endif]-->
<div class="loader">Old Fashion Baptist<p class="address">5003 Wynne Ave | Butte, MT 59701</p></div>
<!--[if !IE]><!-->
<div class="loader-small">Please Wait...</div>
<!--<![endif]-->
<p class="service-times"><b>Service Times</b></p>
<p class="service-times">Sunday School: 10 AM</p>
<p class="service-times">Sunday Morning: 11 AM</p>
<p class="service-times">Sunday Evening: 7 PM</p>
<p class="service-times">Wednesday Evening: 7 PM</p>
</div>
</app-root>
</body>
</html>

View File

@ -0,0 +1,31 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

12
Client/src/main.ts 100644
View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -0,0 +1,85 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
* This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
*/
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1,60 @@
/* You can add global styles to this file, and also import other style files */
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
*{
padding: 0px;
margin: 0px;
box-sizing: border-box;
}
html, body{
height: 100%;
}
body {
height: calc(100% - 50px); /*Less 50 px for height of static header*/
}
.red{
color: red;
}
/*MATERIAL FONT FOR ICONS*/
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(./assets/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(./assets/fonts/MaterialIcons-Regular.woff2) format('woff2'),
url(./assets/fonts/MaterialIcons-Regular.woff) format('woff'),
url(./assets/fonts/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icon {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
/* END MATERIAL FONT ICONS*/

20
Client/src/test.ts 100644
View File

@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,18 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -0,0 +1,22 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2015",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}

131
Client/tslint.json 100644
View File

@ -0,0 +1,131 @@
{
"rulesDirectory": [
"codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}