Skip to content

Commit

Permalink
Upgraded Bulma, added dark mode
Browse files Browse the repository at this point in the history
  • Loading branch information
oktaal committed Jul 4, 2024
1 parent af0d453 commit eb4c5b1
Show file tree
Hide file tree
Showing 26 changed files with 419 additions and 87 deletions.
2 changes: 2 additions & 0 deletions {{cookiecutter.slug}}/frontend.angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Run `ng generate component component-name` to generate a new component. You can

Run `yarn build` to build the project. The build artifacts will be stored in the `dist/` directory.

With SSR (Server Side Rendering) all the routes will be pre-compiled. Because of this the backend server should already be running before building the frontend!

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
Expand Down
31 changes: 31 additions & 0 deletions {{cookiecutter.slug}}/frontend.angular/angular.overwrite.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,37 @@
"src/favicon.png",
"src/favicon.svg",
"src/assets"
],
"styles": [
"src/styles.scss",
{
"input": "src/dark.scss",
"bundleName": "dark",
"inject": false
},
{
"input": "src/light.scss",
"bundleName": "light",
"inject": false
}
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"sourceMap": true
},
{% set comma = joiner(",") %}
{%- for loc in localizations %}
{%- set code, name = loc.split(':') %}
Expand All @@ -50,6 +78,9 @@
"defaultConfiguration": "production"
},
"serve": {
"options": {
"proxyConfig": "../proxy.conf.json"
},
"configurations": {
{% set comma = joiner(",") %}
{%- for loc in localizations %}
Expand Down
7 changes: 3 additions & 4 deletions {{cookiecutter.slug}}/frontend.angular/package.overwrite.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
"build": "ng build --base-href=/static/ --localize",
"i18n": "ng extract-i18n --output-path locale",
"preserve": "yarn prebuild",
"serve": "ng serve --proxy-config ../proxy.conf.json",
"serve": "ng serve",
"start": "yarn serve",
{% set localizations = cookiecutter.localizations.split(',') %}
{%- for loc in localizations %}
{%- set code, name = loc.split(':') %}
"serve:{{code}}": "ng serve --proxy-config ../proxy.conf.json --configuration={{code}}",
"serve:{{code}}": "ng serve --configuration={{code}}",
{%- endfor %}
"stop": "lsof -t -i tcp:{{cookiecutter.frontend_port}} | xargs kill -9 || echo \"not running\"",
"pretest": "yarn prebuild",
Expand All @@ -25,9 +25,8 @@
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@ngrx/effects": "^17.2.0",
"@ngrx/store": "^17.2.0",
"bulma": "^0.9.1",
"bulma": "^1.0.1",
"colors": "^1.4.0",
"primeicons": "^7.0.0",
"primeng": "^17.18.0"
},
"devDependencies": {
Expand Down
21 changes: 20 additions & 1 deletion {{cookiecutter.slug}}/frontend.angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component } from '@angular/core';
import { Component, Inject, afterRender } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { MenuComponent } from './menu/menu.component';
import { FooterComponent } from './footer/footer.component';
import { DarkModeService } from './services/dark-mode.service';

@Component({
selector: '{{cookiecutter.app_prefix}}-root',
Expand All @@ -12,4 +14,21 @@ import { FooterComponent } from './footer/footer.component';
})
export class AppComponent {
title = '{{cookiecutter.project_title}}';

constructor(@Inject(DOCUMENT) private document: Document, private darkModeService: DarkModeService) {
// Using the DOM API to only render on the browser instead of on the server
afterRender(() => {
const style = this.document.createElement('link');
style.rel = 'stylesheet';
this.document.head.append(style);

this.darkModeService.theme$.subscribe(theme => {
this.document.documentElement.classList.remove(theme === 'dark' ? 'theme-light' : 'theme-dark');
this.document.documentElement.classList.add('theme-' + theme);

style.href = `${theme}.css`;
});
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { BACKEND_URL, appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
{ provide: BACKEND_URL, useValue: 'http://localhost:8000' }
]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);
23 changes: 16 additions & 7 deletions {{cookiecutter.slug}}/frontend.angular/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { ApplicationConfig, InjectionToken, provideZoneChangeDetection } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';
import { provideHttpClient, withFetch, withXsrfConfiguration } from '@angular/common/http';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const BACKEND_URL = new InjectionToken<string>('BackendUrl');

export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideClientHydration(),
provideHttpClient(withXsrfConfiguration({
cookieName: 'csrftoken',
headerName: 'X-CSRFToken'
})),
provideHttpClient(
withFetch(),
withXsrfConfiguration({
cookieName: 'csrftoken',
headerName: 'X-CSRFToken'
})),
// The language is used as the base_path for finding the right
// static-files. For example /nl/static/main.js
// However the routing is done from a base path starting from
// the root e.g. /home
// The server should then switch index.html based on a language
// cookie with a fallback to Dutch e.g. /nl/static/index.html
{ provide: APP_BASE_HREF, useValue: '/' }
{ provide: APP_BASE_HREF, useValue: '/' },
// because proxy doesn't work for SSR, support a wonky workaround
// by manually specifying the URL where the backend is running
// https://github.com/angular/angular-cli/issues/27144
// By default it is empty, because in the browser this isn't needed
{ provide: BACKEND_URL, useValue: '' }
]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<p class="control">
@if (dark) {
<a class="button" title="Switch to light mode" i18n-title (click)="toggle()">
<span class="icon">
<fa-icon [icon]="faMoon"></fa-icon>
</span>
</a>
}
@else {
<a class="button" title="Switch to dark mode" i18n-title (click)="toggle()">
<span class="icon">
<fa-icon [icon]="faSun"></fa-icon>
</span>
</a>
}
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DarkModeToggleComponent } from './dark-mode-toggle.component';

describe('DarkModeToggleComponent', () => {
let component: DarkModeToggleComponent;
let fixture: ComponentFixture<DarkModeToggleComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DarkModeToggleComponent]
})
.compileComponents();

fixture = TestBed.createComponent(DarkModeToggleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Component, OnDestroy } from '@angular/core';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faSun, faMoon } from '@fortawesome/free-solid-svg-icons';
import { Subscription } from 'rxjs';
import { DarkModeService } from '../services/dark-mode.service';

@Component({
selector: '{{cookiecutter.app_prefix}}-dark-mode-toggle',
standalone: true,
imports: [FontAwesomeModule],
templateUrl: './dark-mode-toggle.component.html',
styleUrl: './dark-mode-toggle.component.scss'
})
export class DarkModeToggleComponent implements OnDestroy {
private subscriptions!: Subscription[];
faSun = faSun;
faMoon = faMoon;
dark = false;

constructor(private darkModeService: DarkModeService) {
this.subscriptions = [
this.darkModeService.theme$.subscribe(theme => this.dark = theme === 'dark')];
}

toggle() {
this.darkModeService.toggle();
}

ngOnDestroy(): void {
this.subscriptions.forEach(s => s.unsubscribe());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
<h1 class="subtitle" i18n>{{cookiecutter.project_title}}</h1>
<div class="content">
<p>
<a href="{{cookiecutter.origin.replace('github:', 'https://github.com/')}}" i18n>Source code</a>&nbsp;<ng-container i18n>(BSD 3-Clause License)</ng-container>
<a [href]="environment.sourceUrl" target="_blank" i18n>Source code</a>&nbsp;<ng-container i18n>(BSD 3-Clause License)</ng-container>
</p>
<p>
<em><ng-container i18n>Version: </ng-container><span [title]="environment.buildTime">{{"{{environment.version}}"}}</span>&nbsp;<a [href]="environment.sourceUrl" target="_blank" i18n>(source)</a></em>
<em><ng-container i18n>Version: </ng-container><span>{{"{{environment.version}}"}}</span>&nbsp;(<span [title]="environment.buildTime">{{"{{buildTime}}"}})</span></em>
</p>
</div>
</div>
<div class="column has-text-right-tablet is-narrow">
<a class="lab-logo image" href="https://cdh.uu.nl/centre-for-digital-humanities/research-software-lab/" target="_blank">
<img src="{{"{{environment.assets}}"}}/uu-cdh.svg" alt="Centre for Digital Humanities, Utrecht University" i18n-alt height="100" />
<a class="centre-logo image"
href="https://cdh.uu.nl/centre-for-digital-humanities/research-software-lab/" target="_blank">
<img [src]="environment.assets + (dark ? '/uu-cdh-dark.svg' :'/uu-cdh.svg')"
alt="Centre for Digital Humanities, Utrecht University" i18n-alt width="450" height="105" />
</a>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.lab-logo {
max-width: 400px;
.centre-logo {
width: 450px;
max-width: calc(100vw - 7rem);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { Component } from '@angular/core';
import { Component, Inject, LOCALE_ID, OnDestroy } from '@angular/core';
import { formatDate } from '@angular/common';
import { Subscription } from 'rxjs';
import { environment } from '../../environments/environment';
import { DarkModeService } from '../services/dark-mode.service';

@Component({
selector: '{{cookiecutter.app_prefix}}-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss'],
standalone: true
})
export class FooterComponent {
export class FooterComponent implements OnDestroy {
environment = environment;
buildTime!: string;
dark = false;
subscriptions!: Subscription[];

constructor() { }
constructor(@Inject(LOCALE_ID) localeId: string, darkModeService: DarkModeService) {
this.buildTime = formatDate(new Date(environment.buildTime), $localize`:@@dateFormat:MMMM dd, yyyy`, localeId);
this.subscriptions = [
darkModeService.theme$.subscribe(theme => this.dark = theme === 'dark')];
}

ngOnDestroy(): void {
this.subscriptions.forEach(s => s.unsubscribe());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
Home
</a>
</div>
<div class="navbar-end" *ngIf="languages">
<div class="navbar-end">
<{{cookiecutter.app_prefix}}-dark-mode-toggle class="navbar-item"></{{cookiecutter.app_prefix}}-dark-mode-toggle>
@if (languages) {
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
<span class="icon">
Expand All @@ -37,6 +39,7 @@
</a>
</div>
</div>
}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, LOCALE_ID, Inject, OnInit, NgZone } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Component, LOCALE_ID, Inject, OnInit, NgZone, afterRender } from '@angular/core';
import { CommonModule, DOCUMENT } from '@angular/common';
import { RouterLink } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faGlobe, faSync } from '@fortawesome/free-solid-svg-icons';
import { animations, showState } from '../animations';
import { DarkModeToggleComponent } from '../dark-mode-toggle/dark-mode-toggle.component';
import { LanguageInfo, LanguageService } from '../services/language.service';

@Component({
Expand All @@ -12,10 +13,10 @@ import { LanguageInfo, LanguageService } from '../services/language.service';
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
standalone: true,
imports: [CommonModule, RouterLink, FontAwesomeModule]
imports: [CommonModule, RouterLink, FontAwesomeModule, DarkModeToggleComponent]
})
export class MenuComponent implements OnInit {
burgerShow: showState;
burgerShow: showState = 'show';
burgerActive = false;
currentLanguage: string;
loading = false;
Expand All @@ -29,13 +30,18 @@ export class MenuComponent implements OnInit {
languages?: LanguageInfo['supported'];

constructor(
@Inject(DOCUMENT) private document: Document,
@Inject(LOCALE_ID) private localeId: string,
private ngZone: NgZone,
private languageService: LanguageService) {
// Window is undefined on the server
const isDesktop = typeof window !== "undefined" ? window.matchMedia("screen and (min-width: 1024px)").matches : true;
this.burgerShow = isDesktop ? 'show' : 'hide';
this.currentLanguage = this.localeId;

// Using the DOM API to only render on the browser instead of on the server
afterRender(() => {
const window = this.document.defaultView;
const isDesktop = window ? window.matchMedia("screen and (min-width: 1024px)").matches : true;
this.burgerShow = isDesktop ? 'show' : 'hide';
});
}

async ngOnInit(): Promise<void> {
Expand Down
Loading

0 comments on commit eb4c5b1

Please sign in to comment.