Added a bunch of services
This commit is contained in:
parent
1fc86d3614
commit
fde487fc66
@ -17,12 +17,14 @@ COPY . .
|
||||
RUN if [ ! -d "dist" ] && [ ! -d "node_modules" ]; then npm install; fi
|
||||
|
||||
# Build
|
||||
RUN BUILD_MODE=$([ "$NODE_ENV" = "prod" ] && echo "dynmaic-prod" || echo "dynamic") && \
|
||||
RUN BUILD_MODE=$([ "$NODE_ENV" = "prod" ] && echo "prod" || echo "dev") && \
|
||||
if [ ! -d "dist" ]; then npm run "build:$BUILD_MODE"; fi
|
||||
|
||||
# Use Nginx to serve
|
||||
FROM nginx:1.20-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/config/robots.txt /usr/share/nginx/html/robots.txt
|
||||
COPY docker/config/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/scripts/setup-environment.sh /docker-entrypoint.d/setup-environment.sh
|
||||
RUN chmod +x /docker-entrypoint.d/setup-environment.sh
|
||||
EXPOSE 80
|
||||
|
3
docker/scripts/setup-environment.sh
Normal file
3
docker/scripts/setup-environment.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
if [ -n "$ANALYTICS" ]; then sed -i -e "s/:[[:space:]]\?['\"]{{ANALYTICS}}['\"]/:'$ANALYTICS'/g" /usr/share/nginx/html/main*.js; fi
|
21
package-lock.json
generated
21
package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"@angular/router": "^14.2.0",
|
||||
"bootstrap": "^5.2.1",
|
||||
"jquery": "^3.6.1",
|
||||
"ngx-google-analytics": "^14.0.1",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"webstorage-decorators": "^4.2.0",
|
||||
@ -7891,6 +7892,18 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ngx-google-analytics": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-google-analytics/-/ngx-google-analytics-14.0.1.tgz",
|
||||
"integrity": "sha512-PfOtnshSyq15EKevKlFW9IRgH+dTtPG4Q9HJYksuRNYDzjce0eqK3Bf6hz0tAZdyqbzTCyx5g+NgWBfpqQfb2w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=12.0.0",
|
||||
"@angular/core": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nice-napi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||
@ -17380,6 +17393,14 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ngx-google-analytics": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-google-analytics/-/ngx-google-analytics-14.0.1.tgz",
|
||||
"integrity": "sha512-PfOtnshSyq15EKevKlFW9IRgH+dTtPG4Q9HJYksuRNYDzjce0eqK3Bf6hz0tAZdyqbzTCyx5g+NgWBfpqQfb2w==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"nice-napi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"@angular/router": "^14.2.0",
|
||||
"bootstrap": "^5.2.1",
|
||||
"jquery": "^3.6.1",
|
||||
"ngx-google-analytics": "^14.0.1",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"webstorage-decorators": "^4.2.0",
|
||||
|
@ -1,12 +1,16 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgxGoogleAnalyticsModule} from 'ngx-google-analytics';
|
||||
import {environment} from '../environments/environment';
|
||||
import {AppRouting} from './app.routing';
|
||||
import {BannerComponent} from './components/banner/banner.component';
|
||||
import {FooterComponent} from './components/footer/footer.component';
|
||||
import {LogoComponent} from './components/logo/logo.component';
|
||||
import {NavbarComponent} from './components/navbar/navbar.component';
|
||||
import {AppComponent} from './containers/app/app.component';
|
||||
import {BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {MaterialModule} from './material.module';
|
||||
import {PrelaodService} from './services/prelaod.service';
|
||||
import {FourOFourComponent} from './views/404/404.component';
|
||||
import {AboutComponent} from './views/about/about.component';
|
||||
import {AestivaComponent} from './views/events/aestiva/aestiva.component';
|
||||
@ -18,32 +22,40 @@ import {DrillComponent} from './views/reenact/drill/drill.component';
|
||||
import {GettingStartedComponent} from './views/reenact/getting-started/getting-started.component';
|
||||
import {RulesComponent} from './views/reenact/rules/rules.component';
|
||||
|
||||
export const APP_COMPONENTS = [
|
||||
export const APP_COMPONENTS: any[] = [
|
||||
AboutComponent,
|
||||
AestivaComponent,
|
||||
AppComponent,
|
||||
BannerComponent,
|
||||
CalendarComponent,
|
||||
DrillComponent,
|
||||
GettingStartedComponent,
|
||||
HibernaComponent,
|
||||
FooterComponent,
|
||||
FourOFourComponent,
|
||||
GalleryComponent,
|
||||
GettingStartedComponent,
|
||||
HibernaComponent,
|
||||
HomeComponent,
|
||||
LogoComponent,
|
||||
NavbarComponent,
|
||||
RulesComponent
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
declarations: APP_COMPONENTS,
|
||||
imports: [
|
||||
BrowserModule,
|
||||
export const APP_IMPORTS: any[] = [
|
||||
AppRouting,
|
||||
BrowserAnimationsModule,
|
||||
BrowserModule,
|
||||
MaterialModule
|
||||
],
|
||||
]
|
||||
|
||||
if(environment.analytics && (<any>environment.analytics) != '{{ANALYTICS}}')
|
||||
APP_IMPORTS.push(NgxGoogleAnalyticsModule.forRoot(<any>environment.analytics));
|
||||
|
||||
@NgModule({
|
||||
declarations: APP_COMPONENTS,
|
||||
imports: APP_IMPORTS,
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {
|
||||
constructor(preload: PrelaodService) { }
|
||||
}
|
||||
|
@ -13,15 +13,15 @@ import {RulesComponent} from './views/reenact/rules/rules.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', pathMatch: 'full', component: HomeComponent},
|
||||
{path: 'about', component: AboutComponent},
|
||||
{path: 'drill', component: DrillComponent},
|
||||
{path: 'events/aestiva', component: AestivaComponent},
|
||||
{path: 'events/hiberna', component: HibernaComponent},
|
||||
{path: 'events/calendar', component: CalendarComponent},
|
||||
{path: 'gallery', component: GalleryComponent},
|
||||
{path: 'getting-started', component: GettingStartedComponent},
|
||||
{path: 'rules', component: RulesComponent},
|
||||
{path: '**', component: FourOFourComponent}
|
||||
{path: 'about', component: AboutComponent, data: {title: 'About'}},
|
||||
{path: 'drill', component: DrillComponent, data: {title: 'Drill Commands'}},
|
||||
{path: 'events/aestiva', component: AestivaComponent, data: {title: 'Castra Aestiva'}},
|
||||
{path: 'events/hiberna', component: HibernaComponent, data: {title: 'Castra Hiberna'}},
|
||||
{path: 'events/calendar', component: CalendarComponent, data: {title: 'Calendar'}},
|
||||
{path: 'gallery', component: GalleryComponent, data: {title: 'Gallery'}},
|
||||
{path: 'getting-started', component: GettingStartedComponent, data: {title: 'Getting Started'}},
|
||||
{path: 'rules', component: RulesComponent, data: {title: 'Rules & Regulations'}},
|
||||
{path: '**', component: FourOFourComponent, data: {title: '404'}}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
9
src/app/components/logo/logo.component.html
Normal file
9
src/app/components/logo/logo.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="logo">
|
||||
<div class="d-flex" aria-label="Legio XXX">
|
||||
<div>L</div>
|
||||
<div [@slide]="expand ? 'expand' : 'shrink'" style="overflow: hidden">EGIO · </div>
|
||||
<div [@margin]="expand ? 'expand' : 'shrink'" [style.marginLeft]="expand ? '0.25rem' : 0">XXX</div>
|
||||
<div *ngIf="loading">{{dots | async}}</div>
|
||||
</div>
|
||||
<div *ngIf="loadingText" class="logo-footer text-center">{{loadingText}}</div>
|
||||
</div>
|
26
src/app/components/logo/logo.component.scss
Normal file
26
src/app/components/logo/logo.component.scss
Normal file
@ -0,0 +1,26 @@
|
||||
//.logo {
|
||||
// .expandable {
|
||||
// width: 0;
|
||||
// }
|
||||
//
|
||||
// &.expanded {
|
||||
// .expandable {
|
||||
// width: auto;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .logo-segment {
|
||||
// font-family: Arial, sans-serif !important;
|
||||
// overflow: hidden;
|
||||
// padding: 0.3em 0;
|
||||
// width: auto;
|
||||
// }
|
||||
//
|
||||
// .logo-dots {
|
||||
// width: 30px;
|
||||
// }
|
||||
//
|
||||
// .logo-footer {
|
||||
// transform: translate(-15px, -8px);
|
||||
// }
|
||||
//}
|
35
src/app/components/logo/logo.component.ts
Normal file
35
src/app/components/logo/logo.component.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {Observable, timer} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'xxx-logo',
|
||||
templateUrl: './logo.component.html',
|
||||
animations: [
|
||||
trigger('slide', [
|
||||
state('expand', style({width: '*'})),
|
||||
state('shrink', style({width: '0px'})),
|
||||
transition('* => *', [animate('0.5s')])
|
||||
]),
|
||||
trigger('margin', [
|
||||
state('expand', style({marginLeft: '0.25rem'})),
|
||||
state('shrink', style({marginLeft: '0'})),
|
||||
transition('* => *', [animate('0.5s')])
|
||||
])
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LogoComponent {
|
||||
@Input() expand = true;
|
||||
@Input() loading = false;
|
||||
@Input() loadingText = '';
|
||||
|
||||
dots: Observable<string>;
|
||||
|
||||
constructor() {
|
||||
this.dots = timer(0, 1000).pipe(map(i => {
|
||||
return Array(i % 4).fill('.').join('');
|
||||
}));
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
<div>
|
||||
<a class="navbar-brand d-flex align-items-center" routerLink="/" fragment="banner" (click)="scroll('banner')">
|
||||
<img src="assets/img/eagle.png" alt="SPQR" height="45px" width="45px">
|
||||
<div class="px-2">LEGIO · XXX</div>
|
||||
<xxx-logo class="px-2" [expand]="true"></xxx-logo>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-grow-1"></div>
|
||||
|
@ -2,6 +2,7 @@ import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output} from '
|
||||
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
|
||||
import {combineLatest, filter, Subscription} from 'rxjs';
|
||||
import {NAVIGATION} from '../../misc/navigation';
|
||||
import {BreakpointService} from '../../services/breakpoint.service';
|
||||
|
||||
@Component({
|
||||
selector: 'xxx-navbar',
|
||||
@ -20,7 +21,7 @@ export class NavbarComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
@Output() hamburgerClick = new EventEmitter<void>();
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router) { }
|
||||
constructor(private route: ActivatedRoute, private router: Router, public breakpoint: BreakpointService) { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.sub = combineLatest([this.router.events.pipe(filter(e => e instanceof NavigationEnd)), this.route.fragment]).subscribe(([url, frag]) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
import { Component } from '@angular/core';
|
||||
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
|
||||
import {filter} from 'rxjs';
|
||||
import {NavigationEnd, Router} from '@angular/router';
|
||||
import {combineLatest, filter, Subscription} from 'rxjs';
|
||||
import {BreakpointService} from '../../services/breakpoint.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -9,15 +9,22 @@ import {filter} from 'rxjs';
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
private sub?: Subscription;
|
||||
|
||||
mobile = false;
|
||||
open = false;
|
||||
|
||||
constructor(private router: Router, route: ActivatedRoute, breakpointObserver: BreakpointObserver) {
|
||||
router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => this.open = false);
|
||||
breakpointObserver.observe(['(max-width: 750px)']).subscribe(result => {
|
||||
this.mobile = result.matches;
|
||||
constructor(private breakpoint: BreakpointService, private router: Router) {
|
||||
this.sub = combineLatest([
|
||||
router.events.pipe(filter(event => event instanceof NavigationEnd)),
|
||||
breakpoint.isMobile$
|
||||
]).subscribe(([event, mobile]) => {
|
||||
this.mobile = mobile;
|
||||
this.open = !this.mobile;
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if(this.sub) this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
33
src/app/services/analytics.service.ts
Normal file
33
src/app/services/analytics.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import {Injectable, OnDestroy, Optional} from '@angular/core';
|
||||
import {NavigationEnd, Router} from '@angular/router';
|
||||
import {GoogleAnalyticsService} from 'ngx-google-analytics';
|
||||
import {combineLatest, Subscription} from 'rxjs';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {TitleService} from './title.service';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AnalyticsService implements OnDestroy {
|
||||
private sub?: Subscription;
|
||||
|
||||
constructor(@Optional() private analyticsService: GoogleAnalyticsService,
|
||||
private router: Router,
|
||||
private title: TitleService
|
||||
) {
|
||||
if(this.analyticsService) {
|
||||
combineLatest([
|
||||
this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
|
||||
]).subscribe(([navigation]) => {
|
||||
this.analyticsService.pageView(navigation.urlAfterRedirects, this.title.title);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
log(action: string, category?: string, label?: string, value?: any, interaction?: boolean) {
|
||||
if(!this.analyticsService) return;
|
||||
return this.analyticsService.event(action, category, label, value, interaction);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if(this.sub) this.sub.unsubscribe();
|
||||
}
|
||||
}
|
15
src/app/services/breakpoint.service.ts
Normal file
15
src/app/services/breakpoint.service.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
|
||||
import {map, tap} from 'rxjs/operators';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class BreakpointService {
|
||||
private _isMobile?: boolean;
|
||||
get isMobile() { return !!this.isMobile$; }
|
||||
isMobile$ = this.breakpointObserver.observe([Breakpoints.XSmall]).pipe(
|
||||
map(e => e.matches),
|
||||
tap(e => this._isMobile = e)
|
||||
);
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver) { }
|
||||
}
|
12
src/app/services/prelaod.service.ts
Normal file
12
src/app/services/prelaod.service.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AnalyticsService} from './analytics.service';
|
||||
import {BreakpointService} from './breakpoint.service';
|
||||
import {TitleService} from './title.service';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class PrelaodService {
|
||||
constructor(private analytics: AnalyticsService,
|
||||
private breakpoint: BreakpointService,
|
||||
private title: TitleService
|
||||
) { }
|
||||
}
|
51
src/app/services/title.service.ts
Normal file
51
src/app/services/title.service.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class TitleService implements OnDestroy {
|
||||
private readonly orgTitle: string = 'LEGIO · XXX';
|
||||
|
||||
private routeSub?: Subscription;
|
||||
private titleSub?: Subscription;
|
||||
|
||||
get title(): string {
|
||||
const title = this._title.getTitle();
|
||||
if(title.includes(this.orgTitle) && title.length > this.orgTitle.length)
|
||||
return title.substring(this.orgTitle.length);
|
||||
return this._title.getTitle();
|
||||
}
|
||||
set title(title: string | null) {
|
||||
if(!title) this._title.setTitle(this.orgTitle);
|
||||
else this._title.setTitle(`${this.orgTitle} | ${title}`);
|
||||
}
|
||||
|
||||
constructor(private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private _title: Title
|
||||
) {
|
||||
// this.orgTitle = this.title; // Hardcoding because HMR breaks this
|
||||
this.routeSub = this.router.events
|
||||
.pipe(filter(e => e instanceof NavigationEnd))
|
||||
.subscribe(() => this.getTitleFromRoute());
|
||||
this.getTitleFromRoute();
|
||||
}
|
||||
|
||||
private async getTitleFromRoute() {
|
||||
if(this.titleSub) this.titleSub.unsubscribe();
|
||||
let route = this.route, title = route.snapshot.data['title'];
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
if(route.snapshot.data['title']) title = route.snapshot.data['title'];
|
||||
}
|
||||
if(title instanceof Observable) { this.titleSub = title.subscribe(t => this.title = t); }
|
||||
else { this.title = title; }
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if(this.routeSub) this.routeSub.unsubscribe();
|
||||
if(this.titleSub) this.titleSub.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export const environment = {
|
||||
analytics: 'G-7HLT4FQY9V',
|
||||
production: true
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
export const environment = {
|
||||
analytics: false,
|
||||
production: false
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user