Add IPFS theme

merge-requests/3/merge
Nato Boram 2020-07-08 20:33:19 -04:00
parent f55110d6df
commit dbeb7b3e22
No known key found for this signature in database
GPG Key ID: 478E3C64BF88AFFA
15 changed files with 201 additions and 33 deletions

View File

@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
- Support for subdomain gateways - Support for subdomain gateways
- Strict mode - Strict mode
- More icons - More icons
- IPFS colours
### Changed ### Changed

View File

@ -1 +1,10 @@
<mat-toolbar color="primary" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="1em">
<h1>Public IPFS Cacher</h1>
<!-- Theme Switcher -->
<button mat-icon-button="">
<mat-icon (click)="themeService.switchTheme()">{{themeService.icon}}</mat-icon>
</button>
</mat-toolbar>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ThemeService } from './services/theme.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -6,5 +7,7 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent { export class AppComponent {
title = 'public-gateway-cacher'; constructor(
public readonly themeService: ThemeService
) { }
} }

View File

@ -1,6 +1,10 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
@ -16,6 +20,12 @@ import { AppComponent } from './app.component';
BrowserAnimationsModule, BrowserAnimationsModule,
HttpClientModule, HttpClientModule,
AppRoutingModule, AppRoutingModule,
// Material
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatToolbarModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,4 @@
export enum Theme {
Light = 'theme-light',
Dark = 'theme-dark',
}

View File

@ -1,12 +1,10 @@
<div class="container" fxLayout="column" fxLayoutAlign="space-evenly center" fxLayoutGap="1em"> <div class="container" fxLayout="column" fxLayoutAlign="space-evenly center" fxLayoutGap="1em">
<h1>Public IPFS Cacher</h1>
<!-- IPFS --> <!-- IPFS -->
<form fxLayout="row" fxLayoutAlign="space-evenly center" fxLayoutGap="1em"> <form fxLayout="row" fxLayoutAlign="space-evenly center" fxLayoutGap="1em">
<!-- Hash --> <!-- Hash -->
<mat-form-field> <mat-form-field [color]="inputColour">
<mat-label>IPFS</mat-label> <mat-label>IPFS</mat-label>
<input name="ipfs" [(ngModel)]="ipfs" matInput> <input name="ipfs" [(ngModel)]="ipfs" matInput>
</mat-form-field> </mat-form-field>
@ -20,7 +18,7 @@
<form fxLayout="row" fxLayoutAlign="space-evenly center" fxLayoutGap="1em"> <form fxLayout="row" fxLayoutAlign="space-evenly center" fxLayoutGap="1em">
<!-- Hash --> <!-- Hash -->
<mat-form-field> <mat-form-field [color]="inputColour">
<mat-label>IPNS</mat-label> <mat-label>IPNS</mat-label>
<input name="ipns" [(ngModel)]="ipns" matInput> <input name="ipns" [(ngModel)]="ipns" matInput>
</mat-form-field> </mat-form-field>
@ -30,7 +28,8 @@
</form> </form>
<mat-progress-bar [value]="dataSource.data.length / gateways.length * 100" *ngIf="subscriptions.length" mode="determinate"> <mat-progress-bar [value]="dataSource.data.length / gateways.length * 100" *ngIf="subscriptions.length" mode="determinate"
color="primary">
</mat-progress-bar> </mat-progress-bar>
<table class="mat-elevation-z8" [dataSource]="dataSource" mat-table> <table class="mat-elevation-z8" [dataSource]="dataSource" mat-table>
@ -47,15 +46,15 @@
<ng-container matColumnDef="gateway"> <ng-container matColumnDef="gateway">
<th mat-header-cell *matHeaderCellDef> Gateway </th> <th mat-header-cell *matHeaderCellDef> Gateway </th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<div [ngSwitch]="element.error"> <div [ngSwitch]="element.ok">
<div *ngSwitchCase="null"> <div *ngSwitchCase="true">
<strong> <strong>
<a class="aqua" href="{{ element.gateway }}#x-ipfs-companion-no-redirect" target="_blank"> <a class="aqua" href="{{ element.gateway }}#x-ipfs-companion-no-redirect" target="_blank">
{{ element.gateway }} {{ element.gateway }}
</a> </a>
</strong> </strong>
</div> </div>
<div *ngSwitchDefault> <div *ngSwitchCase="false">
<a class="aqua-muted" href="{{ element.gateway }}#x-ipfs-companion-no-redirect" target="_blank"> <a class="aqua-muted" href="{{ element.gateway }}#x-ipfs-companion-no-redirect" target="_blank">
{{ element.gateway }} {{ element.gateway }}
</a> </a>

View File

@ -1,21 +1,25 @@
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, EventEmitter, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatTable, MatTableDataSource } from '@angular/material/table'; import { MatTable, MatTableDataSource } from '@angular/material/table';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { Protocol } from '../enums/protocol.enum'; import { Protocol } from '../enums/protocol.enum';
import { Theme } from '../enums/theme.enum';
import { GatewayService } from '../services/gateway.service'; import { GatewayService } from '../services/gateway.service';
import { ThemeService } from '../services/theme.service';
@Component({ @Component({
selector: 'app-pages', selector: 'app-pages',
templateUrl: './pages.component.html', templateUrl: './pages.component.html',
styleUrls: ['./pages.component.scss'] styleUrls: ['./pages.component.scss']
}) })
export class PagesComponent implements OnInit { export class PagesComponent implements OnInit, OnDestroy {
@ViewChild(MatTable) matTable!: MatTable<Result>; @ViewChild(MatTable) matTable!: MatTable<Result>;
gateways!: string[]; gateways!: string[];
inputColour = 'primary';
ipfs = ''; ipfs = '';
ipns = ''; ipns = '';
@ -23,12 +27,27 @@ export class PagesComponent implements OnInit {
readonly displayedColumns: ['icon', 'gateway'] = ['icon', 'gateway']; readonly displayedColumns: ['icon', 'gateway'] = ['icon', 'gateway'];
readonly subscriptions: Subscription[] = []; readonly subscriptions: Subscription[] = [];
private readonly destroy$ = new EventEmitter<void>();
constructor( constructor(
private readonly gatewayService: GatewayService private readonly gatewayService: GatewayService,
private readonly themeService: ThemeService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.gatewayService.list().subscribe((gateways): void => { this.gateways = gateways; }); this.gatewayService.list().subscribe((gateways): void => { this.gateways = gateways; });
// Theme
this.setColours(this.themeService.current);
this.themeService.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((theme): void => {
this.setColours(theme);
});
}
ngOnDestroy(): void {
this.destroy$.complete();
} }
cacheIPFS(): void { cacheIPFS(): void {
@ -62,14 +81,16 @@ export class PagesComponent implements OnInit {
this.dataSource.data.push({ this.dataSource.data.push({
gateway: this.gatewayService.url(gateway, protocol, hashpath), gateway: this.gatewayService.url(gateway, protocol, hashpath),
message: resp.statusText, message: resp.statusText,
icon: this.icon(resp.status) icon: this.getIcon(resp.status),
ok: resp.ok,
}); });
this.matTable.renderRows(); this.matTable.renderRows();
}, (error: HttpErrorResponse): void => { }, (error: HttpErrorResponse): void => {
this.dataSource.data.push({ this.dataSource.data.push({
gateway: this.gatewayService.url(gateway, protocol, hashpath), gateway: this.gatewayService.url(gateway, protocol, hashpath),
message: error.statusText, message: error.statusText,
icon: this.icon(error.status) icon: this.getIcon(error.status),
ok: error.ok,
}); });
this.matTable.renderRows(); this.matTable.renderRows();
}) })
@ -77,7 +98,20 @@ export class PagesComponent implements OnInit {
}); });
} }
private icon(status: number): string { private setColours(theme: Theme): void {
switch (theme) {
case Theme.Light:
this.inputColour = 'primary';
break;
case Theme.Dark:
this.inputColour = 'accent';
break;
default:
break;
}
}
private getIcon(status: number): string {
if (status >= 200 && status < 300) return '✅'; if (status >= 200 && status < 300) return '✅';
switch (status) { switch (status) {
case 0: return '❌'; case 0: return '❌';
@ -94,4 +128,5 @@ interface Result {
gateway: string; gateway: string;
message: string; message: string;
icon: string; icon: string;
ok: boolean;
} }

View File

@ -1,4 +1,4 @@
import { HttpClient, HttpResponseBase } from '@angular/common/http'; import { HttpClient, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
@ -26,9 +26,10 @@ export class GatewayService {
throw new Error('Couldn\'t find environment nor base.') throw new Error('Couldn\'t find environment nor base.')
} }
get(gateway: string, protocol: Protocol, hashpath: string): Observable<HttpResponseBase> { get(gateway: string, protocol: Protocol, hashpath: string): Observable<HttpResponse<string>> {
return this.http.get<HttpResponseBase>(`${this.url(gateway, protocol, hashpath)}#x-ipfs-companion-no-redirect`, { return this.http.get(`${this.url(gateway, protocol, hashpath)}#x-ipfs-companion-no-redirect`, {
observe: 'response' observe: 'response',
responseType: 'text',
}); });
} }

View File

@ -0,0 +1,15 @@
import { TestBed } from '@angular/core/testing';
import { ThemeService } from './theme.service';
describe('ThemeService', (): void => {
let service: ThemeService;
beforeEach((): void => {
TestBed.configureTestingModule({});
service = TestBed.inject(ThemeService);
});
it('should be created', (): void => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Theme } from '../enums/theme.enum';
function enumGuard<T>(enumeration: T): (token: unknown) => token is T[keyof T] {
return (token: unknown): token is T[keyof T] => Object.values(enumeration).includes(token);
}
@Injectable({
providedIn: 'root'
})
export class ThemeService {
current = Theme.Light;
icon = 'brightness_low';
readonly valueChanges: Observable<Theme>;
private readonly subject$ = new Subject<Theme>();
constructor() {
const stored = localStorage.getItem('theme');
if (enumGuard(Theme)(stored)) {
this.setTheme(stored);
}
this.valueChanges = this.subject$.asObservable();
}
switchTheme(): void {
this.setTheme(this.current === Theme.Light ? Theme.Dark : Theme.Light)
}
setTheme(theme: Theme): void {
document.querySelector('body')?.classList.remove(this.current)
document.querySelector('body')?.classList.add(theme)
this.current = theme;
this.icon = this.getIcon(theme)
this.subject$.next(theme);
localStorage.setItem('theme', theme);
}
getIcon(theme: Theme): string {
switch (theme) {
case Theme.Dark: return 'brightness_high';
case Theme.Light:
default: return 'brightness_low';
}
}
}

View File

@ -19,7 +19,7 @@
</head> </head>
<body class="mat-app-background"> <body class="mat-app-background theme-light">
<app-root></app-root> <app-root></app-root>
</body> </body>

View File

@ -8,22 +8,32 @@
// Be sure that you only ever include this mixin once! // Be sure that you only ever include this mixin once!
@include mat-core(); @include mat-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss // // Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker // // (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/ // // hue. Available color palettes: https://material.io/design/color/
$public-gateway-cacher-primary: mat-palette($mat-indigo); // $public-gateway-cacher-primary: mat-palette($mat-indigo);
$public-gateway-cacher-accent: mat-palette($mat-pink, A200, A100, A400); // $public-gateway-cacher-accent: mat-palette($mat-pink, A200, A100, A400);
// The warn palette is optional (defaults to red). // // The warn palette is optional (defaults to red).
$public-gateway-cacher-warn: mat-palette($mat-red); // $public-gateway-cacher-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes). // // Create the theme object (a Sass map containing all of the palettes).
$public-gateway-cacher-theme: mat-light-theme($public-gateway-cacher-primary, $public-gateway-cacher-accent, $public-gateway-cacher-warn); // $public-gateway-cacher-theme: mat-light-theme($public-gateway-cacher-primary, $public-gateway-cacher-accent, $public-gateway-cacher-warn);
// Include theme styles for core and each component used in your app. // // Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component // // Alternatively, you can import and @include the theme mixins for each component
// that you are using. // // that you are using.
@include angular-material-theme($public-gateway-cacher-theme); // @include angular-material-theme($public-gateway-cacher-theme);
@import 'styles/ipfs-themes.scss';
.theme-light {
@include angular-material-theme($ipfs-light-theme);
}
.theme-dark {
@include angular-material-theme($ipfs-dark-theme);
}
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,23 @@
$ipfs-colour-navy: (default: #0B3A53,
lighter: #3e6480,
darker: #00142a,
contrast: (default: $light-primary-text,
lighter: $light-primary-text,
darker: $light-primary-text,
));
$ipfs-colour-aqua: (default: #69c5cd,
lighter: #9df8ff,
darker: #32949c,
contrast: (default: $dark-primary-text,
lighter: $dark-primary-text,
darker: $dark-primary-text,
));
$ipfs-colour-yellow: (default: #f39021,
lighter: #ffc155,
darker: #bb6200,
contrast: (default: $dark-primary-text,
lighter: $dark-primary-text,
darker: $dark-primary-text,
));

View File

@ -0,0 +1,5 @@
@import 'ipfs-colours.scss';
$ipfs-primary: mat-palette($ipfs-colour-navy, 'default', 'lighter', 'darker');
$ipfs-accent: mat-palette($ipfs-colour-aqua, 'default', 'lighter', 'darker');
$ipfs-warn: mat-palette($ipfs-colour-yellow, 'default', 'lighter', 'darker');

View File

@ -0,0 +1,4 @@
@import 'ipfs-palettes.scss';
$ipfs-light-theme: mat-light-theme($ipfs-primary, $ipfs-accent, $ipfs-warn);
$ipfs-dark-theme: mat-dark-theme($ipfs-primary, $ipfs-accent, $ipfs-warn);