Add IPFS theme
parent
f55110d6df
commit
dbeb7b3e22
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum Theme {
|
||||||
|
Light = 'theme-light',
|
||||||
|
Dark = 'theme-dark',
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
));
|
|
@ -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');
|
|
@ -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);
|
Loading…
Reference in New Issue