Migrate some apps to components

Using dynamic loading to take advantage of routerLink instead of anchor links
This commit is contained in:
Daniel J. Summers 2019-11-10 23:12:22 -06:00
parent 7654dfa0dd
commit e0af5d19b2
39 changed files with 759 additions and 264 deletions

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { ApplicationComponent } from './applications/application/application.component'
import { ApplicationComponent } from './applications/application.component'
import { ApplicationListComponent } from './applications/application-list/application-list.component'
import { HomeComponent } from './pages/home/home.component'
import { InformationPublicizingComponent } from './pages/about/information-publicizing.component'

View File

@ -0,0 +1,13 @@
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-all-solutions-link',
template: `<p><br><a routerLink="/solutions">&laquo; Back to All Solutions</a></p>`
})
export class AllSolutionsLinkComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,6 @@
import { Type } from '@angular/core'
/** An item representing an app */
export class AppItem {
constructor(public name: string, public component: Type<any>) { }
}

View File

@ -0,0 +1,10 @@
import { Directive, ViewContainerRef } from '@angular/core'
@Directive({
selector: '[app-application-detail]'
})
export class ApplicationDetailDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@ -0,0 +1,10 @@
<h1>
{{ app.name }}<br>
<small><small>
<a *ngIf="linkToApp" [href]="app.url">{{ app.url }}</a>
<span *ngIf="!linkToApp">{{ app.url }}</span>
<span *ngIf="linkToArchive">&nbsp; &nbsp;
<a [href]="app.archiveUrl"><small>(Archive)</small></a>
</span>
</small></small>
</h1>

View File

@ -0,0 +1,2 @@
h1
line-height: 1.6rem

View File

@ -0,0 +1,28 @@
import { Component, OnInit, Input } from '@angular/core'
import { App } from '../application.types'
@Component({
selector: 'app-application-header',
templateUrl: './application-header.component.html',
styleUrls: ['./application-header.component.sass']
})
export class ApplicationHeaderComponent implements OnInit {
@Input() app: App
constructor() { }
ngOnInit() { }
/** Whether to link to the app's URL */
get linkToApp () {
return this.app.isActive || this.app.linkInactive
}
/** Whether to link to an archive URL */
get linkToArchive () {
return !this.app.isActive && !this.app.linkInactive && (this.app.archiveUrl > '')
}
}

View File

@ -0,0 +1 @@
<aside><img [src]="imageLink" [alt]="imageAlt"></aside>

View File

@ -0,0 +1,5 @@
aside
padding-left: 15px
aside img
border: dotted 1px darkgray
border-radius: 10px

View File

@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core'
import { App } from '../application.types'
@Component({
selector: 'app-application-image',
templateUrl: './application-image.component.html',
styleUrls: ['./application-image.component.sass']
})
export class ApplicationImageComponent implements OnInit {
@Input() app: App
constructor() { }
ngOnInit() {
}
/** The link to the screenshot image */
get imageLink () {
return `/assets/screenshots/${this.app.id}.png`
}
/** The alt text for the screenshot image */
get imageAlt () {
return `Screen shot for ${this.app.name}`
}
}

View File

@ -0,0 +1,69 @@
import { Component, OnInit, ViewChild, ComponentFactoryResolver } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ApplicationService } from './application.service'
import { App } from './application.types'
import { AppItem } from './app-item'
import { ApplicationDetailDirective } from './application-detail.directive'
import { AppDetailComponent } from './solutions/app-detail.component'
import { PrayerTrackerComponent } from './solutions/prayer-tracker.component'
import { VirtualPrayerRoomComponent } from './solutions/virtual-prayer-room.component'
import { BayVistaComponent } from './solutions/bay-vista.component'
import { CassyFianoComponent } from './solutions/cassy-fiano.component'
import { DrMelissaClouthierComponent } from './solutions/dr-melissa-clouthier.component'
import { EmeraldMountainChristianSchoolComponent } from './solutions/emerald-mountain-christian-school.component'
import { FutilityClosetComponent } from './solutions/futility-closet.component'
import { HardCorpsWifeComponent } from './solutions/hard-corps-wife.component'
import { LibertyPunditsComponent } from './solutions/liberty-pundits.component'
@Component({
selector: 'app-application',
template: '<ng-template app-application-detail></ng-template>'
})
export class ApplicationComponent implements OnInit {
private apps = [
new AppItem('bay-vista', BayVistaComponent),
new AppItem('cassy-fiano', CassyFianoComponent),
new AppItem('dr-melissa-clouthier', DrMelissaClouthierComponent),
new AppItem('emerald-mountain-christian-school', EmeraldMountainChristianSchoolComponent),
new AppItem('futility-closet', FutilityClosetComponent),
new AppItem('hard-corps-wife', HardCorpsWifeComponent),
new AppItem('liberty-pundits', LibertyPunditsComponent),
new AppItem('prayer-tracker', PrayerTrackerComponent),
new AppItem('virtual-prayer-room', VirtualPrayerRoomComponent)
]
@ViewChild(ApplicationDetailDirective, { static: true }) appDetail: ApplicationDetailDirective
/** The app we're displaying */
application: App
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appService: ApplicationService,
private route: ActivatedRoute
) { }
ngOnInit() {
this.route.params.subscribe(params => this.displayApp(params['appId']))
}
/** Dynamically load the app-ropriate component */
displayApp(appId: string) {
const appComponent = this.apps.find(a => a.name === appId)
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(appComponent.component)
const viewContainerRef = this.appDetail.viewContainerRef
viewContainerRef.clear()
const componentRef = viewContainerRef.createComponent(componentFactory)
this.appService.getApp(appId)
.subscribe(app => {
(<AppDetailComponent>componentRef.instance).app = app
this.application = app
})
}
}

View File

@ -13,33 +13,9 @@ bayVista.categoryId = Category.STATIC
bayVista.frontPageText = 'Biloxi, Mississippi'
bayVista.frontPageOrder = 1
bayVista.indexText = 'Southern Baptist church in Biloxi, Mississippi'
bayVista.paragraphs = [
`Bay Vista Baptist Church has served the spiritual needs of Mississippi&rsquo;s Gulf Coast for decades. They emphasize
serving their community as well; they were a hub for <abbr title="Federal Emergency Management Agency">FEMA</abbr>
during Hurricane Katrina relief and recovery efforts, and they are a relay point for each year&rsquo;s
<a href="https://www.samaritanspurse.org/what-we-do/operation-christmas-child/">Operation Christmas Child</a>
campaign. As of late 2013, the authors of their current website were no longer around, and no one could get to the
site to update it. We proposed setting up a site based on WordPress, where multiple people could have the ability to
maintain the site, reducing the risk of that happening again. We also mentioned that such a site could also serve a
sermon podcast feed, increasing the reach of their ministry.`
]
bayVista.activities = [
new Activity('What We Did (2014)',
`We manually downloaded all the publically-accessible parts of their old site, and used that content to create a
WordPress-based site, updating a few outdated items along the way. We also established a podcast feed for their
sermons. A few months after initially setting up the site, we updated the theme to be more mobile-friendly.`),
new Activity('What We Did (2016)',
`In the nearly three years since we had set up the site, we were the only ones updating it. We had recently migrated
some older blogs to use a static site generator and were impressed with the performance gains. We converted their
site, to include writing a custom template to support the podcast feed; it is now generated along with the rest of
the site.`),
new Activity('What We Still Do',
`Bit Badger Solutions hosts this site; we also host the church e-mail accounts, and publish sermons to their podcast
feed weekly.`)
]
bayVista.techStack = [
new Technology('Hexo', 'static site generation'),
new Technology('Azure', 'podcast file storage')
new Technology('Azure', 'podcast file storage and automated builds')
]
/** Cassy Fiano */
@ -47,23 +23,6 @@ const cassyFiano = new App('cassy-fiano', 'Cassy Fiano', 'http://www.cassyfiano.
cassyFiano.isActive = false
cassyFiano.categoryId = Category.WORDPRESS
cassyFiano.indexText = 'A &ldquo;rising star&rdquo; conservative blogger'
cassyFiano.paragraphs = [
`Cassy Fiano (now Cassy Chesser) began blogging back in 2007 on Blogger. She worked hard to network with other
bloggers, and wrote prolifically. As she approached the end of her first year of blogging, she was about to outgrow
Blogger. She asked in a blog post if anyone had experience with Movable Type, the platform used by another blog to
which she contributed. I replied that I did not, but that I had experience with WordPress.`
]
cassyFiano.activities = [
new Activity('What We Did (2008)',
`We assisted her with finding a theme, and customized that theme to contain the same sidebar elements as her current
Blogger theme. We modified her old Blogger template to send people to her new blog (using redirection) after
displaying a note that the blog had moved.`),
new Activity('What We Did (2012)',
`In July 2012, we began hosting the site, as well as continuing support for theme updates. This joined her military
wife blog <a href="/solutions/hard-corps-wife" title="Hard Corps Wife | Bit Badger Solutions">Hard Corps Wife</a>,
which we had begun hosting in mid-2011.`),
new Activity('What We Still Do', 'Cassy formally decommissioned this site in early 2014.')
]
cassyFiano.techStack = [ new Technology('WordPress', 'blogging (with a custom theme)') ]
/** Daniel J. Summers */
@ -80,25 +39,6 @@ drMelissaClouthier.categoryId = Category.WORDPRESS
drMelissaClouthier.frontPageText = 'Information Pollination'
drMelissaClouthier.frontPageOrder = 1
drMelissaClouthier.indexText = 'Politics, health, podcasts and more'
drMelissaClouthier.paragraphs = [
`Dr. Melissa Clouthier saw our work with
<a href="/solutions/cassy-fiano" title="Cassy Fiano | Bit Badger Solutions">Cassy</a>&rsquo;s site, and asked us to
help her move off Blogger as well. Melissa blogs from the political right, but also covers health issues and social
media. She had been blogging for a several years, and wanted to bring her old content with her to her new site.`
]
drMelissaClouthier.activities = [
new Activity('What We Did (2009)',
`We created a custom theme based on another site, and developed graphics to complement that theme. We also imported
the content from her Blogger site into the WordPress site, and created a featured content template for the front
page.`),
new Activity('What We Did (2018)',
`Melissa decommissioned her site; we took final snapshots of the information there, then assisted with shutting it
down.`)
]
drMelissaClouthier.footnotes = [
`<em>(NOTE: The thumbnail of the site represents a new skin on the original theme; while the theme is the same, Bit
Badger Solutions did not create the graphics.)</em>`
]
drMelissaClouthier.techStack = [ new Technology('WordPress', 'blogging (with a custom theme)') ]
/** Emerald Mountain Christian School */
@ -107,28 +47,6 @@ const emcs = new App('emerald-mountain-christian-school', 'Emerald Mountain Chri
emcs.isActive = false
emcs.linkInactive = true
emcs.indexText = 'Classical, Christ-centered education near Wetumpka, Alabama'
emcs.paragraphs = [
`Emerald Mountain Christian School is a private Christian school founded over 50 years ago. They use the Principle
Approach&reg;, which emphasizes research, reasoning, relating, and recording to help students synthesize the
information they learn, rather than just requiring rote memorization. More information about the school&rsquo;s rich
history can be found on their site.`
]
emcs.activities = [
new Activity('What We Did (2004)',
`They had a website with very basic information and very little styling. We developed a theme (the one in the
thumbnail), based in large part on the design of their printed materials, and they approved the design. Initially,
the site only contained the content from their previous site. We then put their school calendar of events up on
the site, where parents could find the dates for upcoming events. Finally, we put all the material from their
Parent Information Packet online, which helped prospective families learn more about the school before visiting
it.`),
new Activity('What We Did (2011)',
`The underlying engine of the basic website was switched from PHP to an ASP MVC web application, and the back-end
database was switched from MySQL to a PostgreSQL database.`),
new Activity('What We Did (2013)',
`We passed off the content and hosting of the site to a new maintainer. They have since redesigned it; it is
accessible via the URL above, and at
<a href="http://emcspatriots.org" title="EMCS Patriots">EMCSpatriots.org</a>.`)
]
emcs.techStack = [
new Technology('ASP.NET MVC', 'page generation and interactivity'),
new Technology('PostgreSQL', 'data storage')
@ -140,20 +58,6 @@ futilityCloset.categoryId = Category.WORDPRESS
futilityCloset.frontPageText = 'An idler&rsquo;s miscellany of compendious amusements'
futilityCloset.frontPageOrder = 2
futilityCloset.indexText = 'An idler&rsquo;s miscellany of compendious amusements'
futilityCloset.paragraphs = [
`Futility Closet exists as a place to give people a break from the dullness of work, by providing puzzles, anecdotes,
and more. It began on a shared host, but was growing too large and popular for that platform.`
]
futilityCloset.activities = [
new Activity('What We Did',
`We determined what the traffic requirements and size of the blog were, then made some recommendations. Greg Ross,
the site author, decided on one of our recommendations. He had backups of the existing database, so we were able to
set up a server and restore the data onto that new server. We configured WordPress and locked down the server, and
this blog was moved quickly.`),
new Activity('What We Still Do',
`Bit Badger Solutions still hosts Futility Closet, ensuring that the underlying server receives performance and
security upgrades, monitoring site performance, and maintaining regular backups.`)
]
const fcQuote = new Quote('Greg Ross', 'Futility Closet')
fcQuote.full =
`Bit Badger Solutions has been an absolute godsend for Futility Closet. We have been with them since 2010, initially
@ -178,17 +82,6 @@ const hardCorpsWife = new App('hard-corps-wife', 'Hard Corps Wife', 'http://www.
hardCorpsWife.isActive = false
hardCorpsWife.categoryId = Category.WORDPRESS
hardCorpsWife.indexText = 'Cassy&rsquo;s life as a Marine wife'
hardCorpsWife.paragraphs = [
`Capitalizing on the growth from her Cassy Fiano blog, Cassy (now Chesser) began a separate blog in which she could
chronicle her experience as a military spouse.`
]
hardCorpsWife.activities = [
new Activity('What We Did',
'We customized the header and sidebar of the theme, and set up the hardcorpswife.com domain.'),
new Activity('What We Still Do',
`In 2013, Cassy shifted priorities and closed this site down. She can still be found at other places around the
web.`)
]
hardCorpsWife.techStack = [ new Technology('WordPress', 'blogging') ]
/** Liberty Pundits */
@ -196,30 +89,6 @@ const libertyPundits = new App('liberty-pundits', 'Liberty Pundits', 'http://lib
libertyPundits.isActive = false
libertyPundits.categoryId = Category.WORDPRESS
libertyPundits.indexText = 'The home for conservatives'
libertyPundits.paragraphs = [
`At its founding, Liberty Pundits was a joint venture by 3 established bloggers (Melissa Clouthier, Bill Dupray, and
Clyde Middleton) that, in their words, was aimed at becoming the new home for conservatives on the Internet. With the
three of them all being prolific bloggers in their own right, and the help of many contributors, Liberty Pundits was
a bustling hub of information.`
]
libertyPundits.activities = [
new Activity('What We Did',
`Bill and Clyde had been part of Patriot Room, an already-recognized powerhouse, and their desire was for Liberty
Pundits to contain the content that they had contributed to Patriot Room. The technical lead on that blog had moved
on, so we did some divining of what was there. Once we deduced the current setup, we obtained the data from that
site, determined how it would need to be manipulated to become part of a WordPress blog, then accomplished the data
migration. Initially, this was deployed on the same shared hosting account where LibertyPundits.com, their podcast
distribution site, already resided. The site&rsquo;s traffic quickly overwhelmed that solution. They then were
moved by their host to a <abbr title="Virtual Private Server">VPS</abbr>, which performed moderately better, but
still had quite a few issues, mostly related to the site&rsquo;s traffic volume. We recommended a new server
configuration, including migrating from a fully-featured web server to a more lightweight web server, along with
caching, and configured that server. This configuration eliminated the bottlenecks, and enabled them to have
several 100,000+ hit days with no appreciable slowdowns.`),
new Activity('What We Still Do',
`Bit Badger Solutions maintained the server, keeping it current with performance and security upgrades. We also
provided support to the primary 3 bloggers, when they had questions about WordPress or how the site was performing.
The site closed in August of 2011, as the primary authors moved on to other endeavors.`)
]
libertyPundits.techStack = [
new Technology('WordPress', 'blogging'),
new Technology('Custom software', 'data migration')
@ -539,25 +408,6 @@ theSharkTank.activities = [
const vpr = new App('virtual-prayer-room', 'Virtual Prayer Room', 'https://virtualprayerroom.us')
vpr.isActive = false
vpr.indexText = 'Gives prayer warriors access to requests from wherever they may be, and sends them daily updates'
vpr.paragraphs = [
`Many churches have prayer rooms &ndash; rooms set aside for people to come in to pray. Hoffmantown Church in
Albuquerque, New Mexico was one of these churches. However, they had seen the use of this physical prayer room
dwindling over the years. People had become less willing to drive to the church, especially at night, and security
became an issue as well; either prayer warriors had to know how to disable the security system, or the church would
have to remain unlocked.`,
`Having seen our work with the <a href="/solutions/nsx/"
title="Not So Extreme Makeover: Community Edition | Bit Badger Solutions">Not So Extreme Makeover: Community
Edition</a>, the church contacted us to see if something similar could be developed to help their prayer ministry.
The resulting application that was developed extended the prayer room to wherever the prayer warrior can get an
Internet connection! Prayer warriors could enlist right from the site, and had to be approved. Requests and updates
were tracked by date/time, and warriors could record when they&rsquo;ve prayed for a request from the site, or from
clicking a link in the daily e-mail they received with requests from their interest areas. As many prayer needs are
confidential, security and confidentiality were very important. Virtual Prayer Room ensured these by providing
varying security levels for prayer warriors and the ability to mark each request as confidential.`,
`In 2016, Hoffmantown Church elected to begin using another package for their prayer requests. While a few other
churches had expressed interest in it, none ultimately decided to use it; so, in 2017, Virtual Prayer Room was
officially decommissioned.`
]
vpr.techStack = [
new Technology('Custom PHP code', 'the application'),
new Technology('PostgreSQL', 'data storage')

View File

@ -1,35 +0,0 @@
<app-page-title [title]="pageTitle"></app-page-title>
<div *ngIf="application">
<h1>
{{ application.name }}<br>
<small><small>
<a *ngIf="linkToApp" [href]="application.url">{{ application.url }}</a>
<span *ngIf="!linkToApp">{{ application.url }}</span>
<span *ngIf="linkToArchive">&nbsp; &nbsp;
<a [href]="application.archiveUrl"><small>(Archive)</small></a>
</span>
</small></small>
</h1>
<div class="app-info">
<aside><img [src]="imageLink" [alt]="imageAlt"></aside>
<article class="content">
<p *ngFor="let p of application.paragraphs" [innerHtml]="p"></p>
<div *ngFor="let act of application.activities">
<h3>{{ act.heading }}</h3>
<p [innerHtml]="act.narrative"></p>
</div>
<div *ngIf="application.quotes.length > 0">
<h3>What They Say</h3>
<blockquote *ngFor="let q of application.quotes">
<p class="quote" [innerHtml]="q.full"></p>
<p class="source">
&mdash; <strong class="app-info-heading">{{ q.name }}</strong>
<span *ngIf="q.from">, {{ q.from }}</span>
</p>
</blockquote>
</div>
<p *ngFor="let p of application.footnotes" [innerHtml]="p"></p>
<p><br><a routerLink="/solutions">&laquo; Back to All Solutions</a></p>
</article>
</div>
</div>

View File

@ -1,21 +0,0 @@
h1
line-height: 1.6rem
.app-info
display: flex
flex-flow: row-reverse wrap
justify-content: center
aside
padding-left: 15px
aside img
border: dotted 1px darkgray
border-radius: 10px
blockquote
border-left: solid 1px darkgray
margin-left: 25px
padding-left: 15px
.quote
font-style: italic
.source
text-align: right
padding-right: 60px

View File

@ -1,53 +0,0 @@
import { Component, OnInit, Input } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ApplicationService } from '../application.service'
import { App } from '../application.types'
@Component({
selector: 'app-application',
templateUrl: './application.component.html',
styleUrls: ['./application.component.sass']
})
export class ApplicationComponent implements OnInit {
/** The app we're displaying */
application: App
constructor(
private appService: ApplicationService,
private route: ActivatedRoute
) { }
ngOnInit() {
const appId = this.route.snapshot.paramMap.get('appId')
this.appService.getApp(appId)
.subscribe(app => this.application = app)
}
/** The page title based on this app */
get pageTitle () {
return `${this.application.name} « Solutions`
}
/** Whether to link to the app's URL */
get linkToApp () {
return this.application.isActive || this.application.linkInactive
}
/** Whether to link to an archive URL */
get linkToArchive () {
return !this.application.isActive && !this.application.linkInactive && (this.application.archiveUrl > '')
}
/** The link to the screenshot image */
get imageLink () {
return `/assets/screenshots/${this.application.id}.png`
}
/** The alt text for the screenshot image */
get imageAlt () {
return `Screen shot for ${this.application.name}`
}
}

View File

@ -3,15 +3,54 @@ import { CommonModule } from '@angular/common'
import { RouterModule } from '@angular/router'
import { SharedModule } from '../shared/shared.module'
import { ApplicationComponent } from './application/application.component';
import { ApplicationListComponent } from './application-list/application-list.component';
import { ApplicationComponent } from './application.component'
import { ApplicationListComponent } from './application-list/application-list.component'
import { ApplicationListItemComponent } from './application-list-item/application-list-item.component'
import { PrayerTrackerComponent } from './solutions/prayer-tracker.component'
import { ApplicationHeaderComponent } from './application-header/application-header.component'
import { AllSolutionsLinkComponent } from './all-solutions-link.component'
import { ApplicationImageComponent } from './application-image/application-image.component'
import { VirtualPrayerRoomComponent } from './solutions/virtual-prayer-room.component'
import { ApplicationDetailDirective } from './application-detail.directive'
import { BayVistaComponent } from './solutions/bay-vista.component'
import { CassyFianoComponent } from './solutions/cassy-fiano.component'
import { DrMelissaClouthierComponent } from './solutions/dr-melissa-clouthier.component';
import { EmeraldMountainChristianSchoolComponent } from './solutions/emerald-mountain-christian-school.component';
import { FutilityClosetComponent } from './solutions/futility-closet.component';
import { QuotesComponent } from './quotes/quotes.component';
import { HardCorpsWifeComponent } from './solutions/hard-corps-wife.component';
import { LibertyPunditsComponent } from './solutions/liberty-pundits.component'
@NgModule({
declarations: [
ApplicationComponent,
ApplicationListComponent,
ApplicationListItemComponent
ApplicationListItemComponent,
PrayerTrackerComponent,
ApplicationHeaderComponent,
AllSolutionsLinkComponent,
ApplicationImageComponent,
VirtualPrayerRoomComponent,
ApplicationDetailDirective,
BayVistaComponent,
CassyFianoComponent,
DrMelissaClouthierComponent,
EmeraldMountainChristianSchoolComponent,
FutilityClosetComponent,
QuotesComponent,
HardCorpsWifeComponent,
LibertyPunditsComponent
],
entryComponents: [
BayVistaComponent,
CassyFianoComponent,
DrMelissaClouthierComponent,
EmeraldMountainChristianSchoolComponent,
FutilityClosetComponent,
HardCorpsWifeComponent,
LibertyPunditsComponent,
PrayerTrackerComponent,
VirtualPrayerRoomComponent
],
imports: [
CommonModule,

View File

@ -0,0 +1,10 @@
<div *ngIf="(quotes || []).length > 0">
<h3>What They Say</h3>
<blockquote *ngFor="let quote of quotes">
<p class="quote" [innerHtml]="quote.full"></p>
<p class="source">
&mdash; <strong>{{ quote.name }}</strong>
<span *ngIf="quote.from">, {{ quote.from }}</span>
</p>
</blockquote>
</div>

View File

@ -0,0 +1,9 @@
blockquote
border-left: solid 1px darkgray
margin-left: 25px
padding-left: 15px
.quote
font-style: italic
.source
text-align: right
padding-right: 60px

View File

@ -0,0 +1,18 @@
import { Component, OnInit, Input } from '@angular/core'
import { Quote } from '../application.types'
@Component({
selector: 'app-quotes',
templateUrl: './quotes.component.html',
styleUrls: ['./quotes.component.sass']
})
export class QuotesComponent implements OnInit {
@Input() quotes: Quote[]
constructor() { }
ngOnInit() { }
}

View File

@ -0,0 +1,14 @@
import { App } from '../application.types'
/** An inteface implemented by all app detail components */
export class AppDetailComponent {
/** The app to be displayed */
app: App
/** The page title based on this app */
get pageTitle () {
return `${this.app.name} « Solutions`
}
}

View File

@ -0,0 +1,51 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Bay Vista Baptist Church has served the spiritual needs of Mississippi&rsquo;s Gulf Coast for decades. They
emphasize serving their community as well; they were a hub for
<abbr title="Federal Emergency Management Agency">FEMA</abbr> during Hurricane Katrina relief and recovery
efforts, and they are a relay point for each year&rsquo;s
<a href="https://www.samaritanspurse.org/what-we-do/operation-christmas-child/">Operation Christmas Child</a>
campaign. As of late 2013, the authors of their current website were no longer around, and no one could get to
the site to update it. We proposed setting up a site based on WordPress, where multiple people could have the
ability to maintain the site, reducing the risk of that happening again. We also mentioned that such a site could
also serve a sermon podcast feed, increasing the reach of their ministry.
</p>
<div>
<h3>What We Did (2014)</h3>
<p>
We manually downloaded all the publically-accessible parts of their old site, and used that content to create a
WordPress-based site, updating a few outdated items along the way. We also established a podcast feed for their
sermons. A few months after initially setting up the site, we updated the theme to be more mobile-friendly.
</p>
</div>
<div>
<h3>What We Did (2016)</h3>
<p>
In the nearly three years since we had set up the site, we were the only ones updating it. We had recently
migrated some older blogs to use a static site generator and were impressed with the performance gains. We
converted their site, to include writing a custom template to support the podcast feed; it is now generated
along with the rest of the site.
</p>
</div>
<div>
<h3>What We Did (2019)</h3>
<p>
We <a href="https://github.com/bayvistabc/www.bayvista.org">open sourced</a> the site's source code, and set up
Azure Pipelines to automatically build and deploy the site on demand, as well as the regular podcast episode
release time.
</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>
Bit Badger Solutions hosts this site; we also host the church e-mail accounts, and publish sermons to their
podcast feed weekly.
</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-bay-vista',
templateUrl: './bay-vista.component.html'
})
export class BayVistaComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,34 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Cassy Fiano (now Cassy Chesser) began blogging back in 2007 on Blogger. She worked hard to network with other
bloggers, and wrote prolifically. As she approached the end of her first year of blogging, she was about to
outgrow Blogger. She asked in a blog post if anyone had experience with Movable Type, the platform used by
another blog to which she contributed. I replied that I did not, but that I had experience with WordPress.
</p>
<div>
<h3>What We Did (2008)</h3>
<p>
We assisted her with finding a theme, and customized that theme to contain the same sidebar elements as her
current Blogger theme. We modified her old Blogger template to send people to her new blog (using redirection)
after displaying a note that the blog had moved.
</p>
</div>
<div>
<h3>What We Did (2012)</h3>
<p>
In July 2012, we began hosting the site, as well as continuing support for theme updates. This joined her
military wife blog <a routerLink="/solutions/hard-corps-wife"
title="Hard Corps Wife | Bit Badger Solutions">Hard Corps Wife</a>, which we had begun hosting in mid-2011.
</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>Cassy formally decommissioned this site in early 2014.</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-cassy-fiano',
templateUrl: './cassy-fiano.component.html'
})
export class CassyFianoComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,33 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Dr. Melissa Clouthier saw our work with <a routerLink="/solutions/cassy-fiano"
title="Cassy Fiano | Bit Badger Solutions">Cassy</a>&rsquo;s site, and asked us to help her move off Blogger as
well. Melissa blogs from the political right, but also covers health issues and social media. She had been
blogging for a several years, and wanted to bring her old content with her to her new site.
</p>
<div>
<h3>What We Did (2009)</h3>
<p>
We created a custom theme based on another site, and developed graphics to complement that theme. We also
imported the content from her Blogger site into the WordPress site, and created a featured content template for
the front page.
</p>
</div>
<div>
<h3>What We Did (2018)</h3>
<p>
Melissa decommissioned her site; we took final snapshots of the information there, then assisted with shutting
it down.
</p>
</div>
<p>
<small><em>(NOTE: The thumbnail of the site represents a new skin on the original theme; while the theme is the
same, Bit Badger Solutions did not create the graphics.)</em></small>
</p>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-dr-melissa-clouthier',
templateUrl: './dr-melissa-clouthier.component.html'
})
export class DrMelissaClouthierComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,40 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Emerald Mountain Christian School is a private Christian school founded over 50 years ago. They use the Principle
Approach&reg;, which emphasizes research, reasoning, relating, and recording to help students synthesize the
information they learn, rather than just requiring rote memorization. More information about the school&rsquo;s
rich history can be found on their site.
</p>
<div>
<h3>What We Did (2004)</h3>
<p>
They had a website with very basic information and very little styling. We developed a theme (the one in the
thumbnail), based in large part on the design of their printed materials, and they approved the design.
Initially, the site only contained the content from their previous site. We then put their school calendar of
events up on the site, where parents could find the dates for upcoming events. Finally, we put all the material
from their Parent Information Packet online, which helped prospective families learn more about the school
before visiting it.
</p>
</div>
<div>
<h3>What We Did (2011)</h3>
<p>
The underlying engine of the basic website was switched from PHP to an ASP MVC web application, and the
back-end database was switched from MySQL to a PostgreSQL database.
</p>
</div>
<div>
<h3>What We Did (2013)</h3>
<p>
We passed off the content and hosting of the site to a new maintainer. They have since redesigned it; it is
accessible via the URL above, and at <a href="http://emcspatriots.org"
title="EMCS Patriots">EMCSpatriots.org</a>.
</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-emerald-mountain-christian-school',
templateUrl: './emerald-mountain-christian-school.component.html'
})
export class EmeraldMountainChristianSchoolComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,29 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Futility Closet exists as a place to give people a break from the dullness of work, by providing puzzles,
anecdotes, and more. It began on a shared host, but was growing too large and popular for that platform.
</p>
<div>
<h3>What We Did</h3>
<p>
We determined what the traffic requirements and size of the blog were, then made some recommendations. Greg
Ross, the site author, decided on one of our recommendations. He had backups of the existing database, so we
were able to set up a server and restore the data onto that new server. We configured WordPress and locked down
the server, and this blog was moved quickly.
</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>
Bit Badger Solutions still hosts Futility Closet, ensuring that the underlying server receives performance and
security upgrades, monitoring site performance, and maintaining regular backups.
</p>
</div>
<app-quotes [quotes]="app.quotes"></app-quotes>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-futility-closet',
templateUrl: './futility-closet.component.html'
})
export class FutilityClosetComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,23 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Capitalizing on the growth from her Cassy Fiano blog, Cassy (now Chesser) began a separate blog in which she
could chronicle her experience as a military spouse.
</p>
<div>
<h3>What We Did</h3>
<p>We customized the header and sidebar of the theme, and set up the hardcorpswife.com domain.</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>
In 2013, Cassy shifted priorities and closed this site down. She can still be found at other places around the
web.
</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-hard-corps-wife',
templateUrl: './hard-corps-wife.component.html'
})
export class HardCorpsWifeComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,39 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
At its founding, Liberty Pundits was a joint venture by 3 established bloggers (Melissa Clouthier, Bill Dupray,
and Clyde Middleton) that, in their words, was aimed at becoming the new home for conservatives on the Internet.
With the three of them all being prolific bloggers in their own right, and the help of many contributors, Liberty
Pundits was a bustling hub of information.
</p>
<div>
<h3>What We Did</h3>
<p>
Bill and Clyde had been part of Patriot Room, an already-recognized powerhouse, and their desire was for
Liberty Pundits to contain the content that they had contributed to Patriot Room. The technical lead on that
blog had moved on, so we did some divining of what was there. Once we deduced the current setup, we obtained
the data from that site, determined how it would need to be manipulated to become part of a WordPress blog,
then accomplished the data migration. Initially, this was deployed on the same shared hosting account where
LibertyPundits.com, their podcast distribution site, already resided. The site&rsquo;s traffic quickly
overwhelmed that solution. They then were moved by their host to a
<abbr title="Virtual Private Server">VPS</abbr>, which performed moderately better, but still had quite a few
issues, mostly related to the site&rsquo;s traffic volume. We recommended a new server configuration, including
migrating from a fully-featured web server to a more lightweight web server, along with caching, and configured
that server. This configuration eliminated the bottlenecks, and enabled them to have several 100,000+ hit days
with no appreciable slowdowns.
</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>
Bit Badger Solutions maintained the server, keeping it current with performance and security upgrades. We also
provided support to the primary 3 bloggers, when they had questions about WordPress or how the site was
performing. The site closed in August of 2011, as the primary authors moved on to other endeavors.
</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-liberty-pundits',
templateUrl: './liberty-pundits.component.html'
})
export class LibertyPunditsComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,44 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Years ago, Daniel was responsible for keeping up with prayer requests for his Sunday School class. To help him
keep up with requests, automatically drop requests that were old, and track long-term requests, he wrote a custom
app made up of a few pages. Over time, he added security mechanisms and other options, arriving at the site that
exists today. It is provided free for the asking to any church, Sunday School class, or small group that desires
a tool to help them establish a continuous list of prayer requests.
</p>
<div>
<h3>What We Did (2005)</h3>
<p>Created the original site.</p>
</div>
<div>
<h3>What We Did (2011)</h3>
<p>
We rewrote this application using a more modern (at the time) framework (ASP MVC 3), building the security
additions from the ground up, and posturing it for an interface with
<a routerLink="/solutions/virtual-prayer-room" title="Virtual Prayer Room | Bit Badger Solutions">Virtual
Prayer Room</a>.
</p>
</div>
<div>
<h3>What We Did (2012)</h3>
<p>In April 2012, version 4 was released with support for Spanish - our first multi-lingual application!</p>
</div>
<div>
<h3>What We Did (2018)</h3>
<p>Version 7 brought full mobile accessibility, along with an upgrade to a modern, ultra-fast web framework.</p>
</div>
<div>
<h3>What We Did (2019)</h3>
<p>PrayerTracker became <a href="https://github.com/bit-badger/PrayerTracker">an open source project</a>.</p>
</div>
<div>
<h3>What We Still Do</h3>
<p>Host and maintain this application.</p>
</div>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core'
import { AppDetailComponent } from './app-detail.component'
import { App } from '../application.types'
@Component({
selector: 'app-prayer-tracker',
templateUrl: './prayer-tracker.component.html'
})
export class PrayerTrackerComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -0,0 +1,32 @@
<app-page-title [title]="pageTitle"></app-page-title>
<app-application-header [app]="app"></app-application-header>
<div class="app-info">
<app-application-image [app]="app"></app-application-image>
<article class="content">
<p>
Many churches have prayer rooms &ndash; rooms set aside for people to come in to pray. Hoffmantown Church in
Albuquerque, New Mexico was one of these churches. However, they had seen the use of this physical prayer room
dwindling over the years. People had become less willing to drive to the church, especially at night, and
security became an issue as well; either prayer warriors had to know how to disable the security system, or the
church would have to remain unlocked.
</p>
<p>
Having seen our work with the <a routerLink="/solutions/nsx/"
title="Not So Extreme Makeover: Community Edition | Bit Badger Solutions">Not So Extreme Makeover: Community
Edition</a>, the church contacted us to see if something similar could be developed to help their prayer
ministry. The resulting application that was developed extended the prayer room to wherever the prayer warrior
can get an Internet connection! Prayer warriors could enlist right from the site, and had to be approved.
Requests and updates were tracked by date/time, and warriors could record when they&rsquo;ve prayed for a request
from the site, or from clicking a link in the daily e-mail they received with requests from their interest areas.
As many prayer needs are confidential, security and confidentiality were very important. Virtual Prayer Room
ensured these by providing varying security levels for prayer warriors and the ability to mark each request as
confidential.
</p>
<p>
In 2016, Hoffmantown Church elected to begin using another package for their prayer requests. While a few other
churches had expressed interest in it, none ultimately decided to use it; so, in 2017, Virtual Prayer Room was
officially decommissioned.
</p>
<app-all-solutions-link></app-all-solutions-link>
</article>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
import { AppDetailComponent } from './app-detail.component';
import { App } from '../application.types';
@Component({
selector: 'app-virtual-prayer-room',
templateUrl: './virtual-prayer-room.component.html'
})
export class VirtualPrayerRoomComponent extends AppDetailComponent {
@Input() app: App
constructor() {
super()
}
}

View File

@ -52,3 +52,7 @@ ul
padding-left: 40px
li
list-style-type: disc
.app-info
display: flex
flex-flow: row-reverse wrap
justify-content: center