implement theme switching service
This commit is contained in:
parent
ee92f6639a
commit
4c205eb09d
@ -1,4 +1,4 @@
|
|||||||
export const mempoolFeeColors = [
|
export const defaultMempoolFeeColors = [
|
||||||
'557d00',
|
'557d00',
|
||||||
'5d7d01',
|
'5d7d01',
|
||||||
'637d02',
|
'637d02',
|
||||||
@ -39,6 +39,47 @@ export const mempoolFeeColors = [
|
|||||||
'ae005b',
|
'ae005b',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const contrastMempoolFeeColors = [
|
||||||
|
'83fd00',
|
||||||
|
'83f609',
|
||||||
|
'83ef12',
|
||||||
|
'83e71a',
|
||||||
|
'83e023',
|
||||||
|
'83d92c',
|
||||||
|
'83d235',
|
||||||
|
'83cb3e',
|
||||||
|
'83c446',
|
||||||
|
'83bc4f',
|
||||||
|
'83b558',
|
||||||
|
'83ae61',
|
||||||
|
'83a76a',
|
||||||
|
'83a072',
|
||||||
|
'83997b',
|
||||||
|
'839184',
|
||||||
|
'838a8d',
|
||||||
|
'838395',
|
||||||
|
'837c9e',
|
||||||
|
'8375a7',
|
||||||
|
'836eb0',
|
||||||
|
'8366b9',
|
||||||
|
'835fc1',
|
||||||
|
'8358ca',
|
||||||
|
'8351d3',
|
||||||
|
'834adc',
|
||||||
|
'8343e5',
|
||||||
|
'833bed',
|
||||||
|
'8334f6',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
'832dff',
|
||||||
|
];
|
||||||
|
|
||||||
export const chartColors = [
|
export const chartColors = [
|
||||||
"#D81B60",
|
"#D81B60",
|
||||||
"#8E24AA",
|
"#8E24AA",
|
||||||
|
@ -7,6 +7,7 @@ import TxView from './tx-view';
|
|||||||
import { Color, Position } from './sprite-types';
|
import { Color, Position } from './sprite-types';
|
||||||
import { Price } from '../../services/price.service';
|
import { Price } from '../../services/price.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
||||||
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
||||||
@ -55,6 +56,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
@ViewChild('blockCanvas')
|
@ViewChild('blockCanvas')
|
||||||
canvas: ElementRef<HTMLCanvasElement>;
|
canvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
themeChangedSubscription: Subscription;
|
||||||
|
|
||||||
gl: WebGLRenderingContext;
|
gl: WebGLRenderingContext;
|
||||||
animationFrameRequest: number;
|
animationFrameRequest: number;
|
||||||
@ -86,6 +88,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
readonly ngZone: NgZone,
|
readonly ngZone: NgZone,
|
||||||
readonly elRef: ElementRef,
|
readonly elRef: ElementRef,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||||
@ -104,6 +107,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (this.gl) {
|
if (this.gl) {
|
||||||
this.initCanvas();
|
this.initCanvas();
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||||
|
// force full re-render
|
||||||
|
this.resizeCanvas();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +156,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||||
|
this.themeChangedSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +299,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.start();
|
this.start();
|
||||||
} else {
|
} else {
|
||||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
||||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
|
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService,
|
||||||
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
|
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
|
||||||
colorFunction: this.getColorFunction() });
|
colorFunction: this.getColorFunction() });
|
||||||
this.start();
|
this.start();
|
||||||
|
@ -3,12 +3,14 @@ import TxView from './tx-view';
|
|||||||
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
||||||
import { defaultColorFunction } from './utils';
|
import { defaultColorFunction } from './utils';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
export default class BlockScene {
|
export default class BlockScene {
|
||||||
scene: { count: number, offset: { x: number, y: number}};
|
scene: { count: number, offset: { x: number, y: number}};
|
||||||
vertexArray: FastVertexArray;
|
vertexArray: FastVertexArray;
|
||||||
txs: { [key: string]: TxView };
|
txs: { [key: string]: TxView };
|
||||||
getColor: ((tx: TxView) => Color) = defaultColorFunction;
|
getColor: ((tx: TxView) => Color) = defaultColorFunction;
|
||||||
|
theme: ThemeService;
|
||||||
orientation: string;
|
orientation: string;
|
||||||
flip: boolean;
|
flip: boolean;
|
||||||
animationDuration: number = 900;
|
animationDuration: number = 900;
|
||||||
@ -29,11 +31,11 @@ export default class BlockScene {
|
|||||||
animateUntil = 0;
|
animateUntil = 0;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|
||||||
constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }:
|
constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
||||||
) {
|
) {
|
||||||
this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction });
|
this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction });
|
||||||
}
|
}
|
||||||
|
|
||||||
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
||||||
@ -90,7 +92,7 @@ export default class BlockScene {
|
|||||||
});
|
});
|
||||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
txs.forEach(tx => {
|
txs.forEach(tx => {
|
||||||
const txView = new TxView(tx, this);
|
const txView = new TxView(tx, this, this.theme);
|
||||||
this.txs[tx.txid] = txView;
|
this.txs[tx.txid] = txView;
|
||||||
this.place(txView);
|
this.place(txView);
|
||||||
this.saveGridToScreenPosition(txView);
|
this.saveGridToScreenPosition(txView);
|
||||||
@ -136,7 +138,7 @@ export default class BlockScene {
|
|||||||
});
|
});
|
||||||
txs.forEach(tx => {
|
txs.forEach(tx => {
|
||||||
if (!this.txs[tx.txid]) {
|
if (!this.txs[tx.txid]) {
|
||||||
this.txs[tx.txid] = new TxView(tx, this);
|
this.txs[tx.txid] = new TxView(tx, this, this.theme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -178,7 +180,7 @@ export default class BlockScene {
|
|||||||
if (resetLayout) {
|
if (resetLayout) {
|
||||||
add.forEach(tx => {
|
add.forEach(tx => {
|
||||||
if (!this.txs[tx.txid]) {
|
if (!this.txs[tx.txid]) {
|
||||||
this.txs[tx.txid] = new TxView(tx, this);
|
this.txs[tx.txid] = new TxView(tx, this, this.theme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||||
@ -198,7 +200,7 @@ export default class BlockScene {
|
|||||||
|
|
||||||
// try to insert new txs directly
|
// try to insert new txs directly
|
||||||
const remaining = [];
|
const remaining = [];
|
||||||
add.map(tx => new TxView(tx, this)).sort(feeRateDescending).forEach(tx => {
|
add.map(tx => new TxView(tx, this, this.theme)).sort(feeRateDescending).forEach(tx => {
|
||||||
if (!this.tryInsertByFee(tx)) {
|
if (!this.tryInsertByFee(tx)) {
|
||||||
remaining.push(tx);
|
remaining.push(tx);
|
||||||
}
|
}
|
||||||
@ -228,9 +230,9 @@ export default class BlockScene {
|
|||||||
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }:
|
private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
||||||
): void {
|
): void {
|
||||||
this.animationDuration = animationDuration || 1000;
|
this.animationDuration = animationDuration || 1000;
|
||||||
this.configAnimationOffset = animationOffset;
|
this.configAnimationOffset = animationOffset;
|
||||||
@ -240,6 +242,7 @@ export default class BlockScene {
|
|||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
this.highlightingEnabled = highlighting;
|
this.highlightingEnabled = highlighting;
|
||||||
this.getColor = colorFunction || defaultColorFunction;
|
this.getColor = colorFunction || defaultColorFunction;
|
||||||
|
this.theme = theme;
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -5,6 +5,8 @@ import { hexToColor } from './utils';
|
|||||||
import BlockScene from './block-scene';
|
import BlockScene from './block-scene';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { TransactionFlags } from '../../shared/filters.utils';
|
import { TransactionFlags } from '../../shared/filters.utils';
|
||||||
|
import { feeLevels } from '../../app.constants';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
const hoverTransitionTime = 300;
|
const hoverTransitionTime = 300;
|
||||||
const defaultHoverColor = hexToColor('1bd8f4');
|
const defaultHoverColor = hexToColor('1bd8f4');
|
||||||
@ -36,6 +38,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
theme: ThemeService;
|
||||||
|
|
||||||
initialised: boolean;
|
initialised: boolean;
|
||||||
vertexArray: FastVertexArray;
|
vertexArray: FastVertexArray;
|
||||||
@ -50,7 +53,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|
||||||
constructor(tx: TransactionStripped, scene: BlockScene) {
|
constructor(tx: TransactionStripped, scene: BlockScene, theme: ThemeService) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.context = tx.context;
|
this.context = tx.context;
|
||||||
this.txid = tx.txid;
|
this.txid = tx.txid;
|
||||||
@ -66,6 +69,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n;
|
this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n;
|
||||||
this.initialised = false;
|
this.initialised = false;
|
||||||
this.vertexArray = scene.vertexArray;
|
this.vertexArray = scene.vertexArray;
|
||||||
|
this.theme = theme;
|
||||||
|
|
||||||
this.hover = false;
|
this.hover = false;
|
||||||
|
|
||||||
@ -138,10 +142,10 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
// Temporarily override the tx color
|
// Temporarily override the tx color
|
||||||
// returns minimum transition end time
|
// returns minimum transition end time
|
||||||
setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): number {
|
setHover(hoverOn: boolean, color: Color | void): number {
|
||||||
if (hoverOn) {
|
if (hoverOn) {
|
||||||
this.hover = true;
|
this.hover = true;
|
||||||
this.hoverColor = color;
|
this.hoverColor = color || this.theme.defaultHoverColor;
|
||||||
|
|
||||||
this.sprite.update({
|
this.sprite.update({
|
||||||
...this.hoverColor,
|
...this.hoverColor,
|
||||||
@ -191,4 +195,30 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
return performance.now() + hoverTransitionTime;
|
return performance.now() + hoverTransitionTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getColor(): Color {
|
||||||
|
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1;
|
||||||
|
const feeLevelColor = this.theme.feeColors[feeLevelIndex] || this.theme.feeColors[this.theme.mempoolFeeColors.length - 1];
|
||||||
|
// Block audit
|
||||||
|
switch(this.status) {
|
||||||
|
case 'censored':
|
||||||
|
return this.theme.auditColors.censored;
|
||||||
|
case 'missing':
|
||||||
|
return this.theme.auditColors.missing;
|
||||||
|
case 'fresh':
|
||||||
|
return this.theme.auditColors.missing;
|
||||||
|
case 'added':
|
||||||
|
return this.theme.auditColors.added;
|
||||||
|
case 'selected':
|
||||||
|
return this.theme.auditColors.selected;
|
||||||
|
case 'found':
|
||||||
|
if (this.context === 'projected') {
|
||||||
|
return this.theme.auditFeeColors[feeLevelIndex] || this.theme.auditFeeColors[this.theme.mempoolFeeColors.length - 1];
|
||||||
|
} else {
|
||||||
|
return feeLevelColor;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return feeLevelColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Observable, combineLatest } from 'rxjs';
|
import { Observable, combineLatest } from 'rxjs';
|
||||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import { map, startWith, tap } from 'rxjs/operators';
|
import { map, startWith, tap } from 'rxjs/operators';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fees-box',
|
selector: 'app-fees-box',
|
||||||
@ -18,7 +19,8 @@ export class FeesBoxComponent implements OnInit {
|
|||||||
noPriority = '#2e324e';
|
noPriority = '#2e324e';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -33,11 +35,11 @@ export class FeesBoxComponent implements OnInit {
|
|||||||
tap((fees) => {
|
tap((fees) => {
|
||||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
||||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||||
const startColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
const startColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||||
|
|
||||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl);
|
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl);
|
||||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||||
const endColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
const endColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||||
|
|
||||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||||
this.noPriority = startColor;
|
this.noPriority = startColor;
|
||||||
|
@ -4,12 +4,13 @@ import { MempoolBlock } from '../../interfaces/websocket.interface';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { map, switchMap, tap } from 'rxjs/operators';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { ThemeService } from 'src/app/services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-blocks',
|
selector: 'app-mempool-blocks',
|
||||||
@ -84,6 +85,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
@ -354,7 +356,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
trimmedFeeRange.forEach((fee: number) => {
|
trimmedFeeRange.forEach((fee: number) => {
|
||||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
||||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||||
gradientColors.push(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
gradientColors.push(this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
gradientColors.forEach((color, i, gc) => {
|
gradientColors.forEach((color, i, gc) => {
|
||||||
|
103
frontend/src/app/services/theme.service.ts
Normal file
103
frontend/src/app/services/theme.service.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { audit, Subject } from 'rxjs';
|
||||||
|
import { Color } from '../components/block-overview-graph/sprite-types';
|
||||||
|
import { defaultMempoolFeeColors, contrastMempoolFeeColors } from '../app.constants';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
const defaultAuditColors = {
|
||||||
|
censored: hexToColor('f344df'),
|
||||||
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
|
added: hexToColor('0099ff'),
|
||||||
|
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||||
|
};
|
||||||
|
const contrastAuditColors = {
|
||||||
|
censored: hexToColor('ffa8ff'),
|
||||||
|
missing: darken(desaturate(hexToColor('ffa8ff'), 0.3), 0.7),
|
||||||
|
added: hexToColor('00bb98'),
|
||||||
|
selected: darken(desaturate(hexToColor('00bb98'), 0.3), 0.7),
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ThemeService {
|
||||||
|
style: HTMLLinkElement;
|
||||||
|
theme: string = 'default';
|
||||||
|
themeChanged$: Subject<string> = new Subject();
|
||||||
|
mempoolFeeColors: string[] = defaultMempoolFeeColors;
|
||||||
|
|
||||||
|
/* block visualization colors */
|
||||||
|
defaultHoverColor: Color;
|
||||||
|
feeColors: Color[];
|
||||||
|
auditFeeColors: Color[];
|
||||||
|
auditColors: { [category: string]: Color } = defaultAuditColors;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private storageService: StorageService,
|
||||||
|
) {
|
||||||
|
const theme = this.storageService.getValue('theme-preference') || 'default';
|
||||||
|
this.apply(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
if (theme !== 'default') {
|
||||||
|
if (theme === 'contrast') {
|
||||||
|
this.mempoolFeeColors = contrastMempoolFeeColors;
|
||||||
|
this.auditColors = contrastAuditColors;
|
||||||
|
}
|
||||||
|
if (!this.style) {
|
||||||
|
this.style = document.createElement('link');
|
||||||
|
this.style.rel = 'stylesheet';
|
||||||
|
this.style.href = `theme-${theme}.css`;
|
||||||
|
document.head.appendChild(this.style);
|
||||||
|
} else {
|
||||||
|
this.style.href = `theme-${theme}.css`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.mempoolFeeColors = defaultMempoolFeeColors;
|
||||||
|
this.auditColors = defaultAuditColors;
|
||||||
|
if (this.style) {
|
||||||
|
this.style.remove();
|
||||||
|
this.style = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateFeeColors();
|
||||||
|
this.storageService.setValue('theme-preference', theme);
|
||||||
|
this.themeChanged$.next(this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFeeColors() {
|
||||||
|
this.defaultHoverColor = hexToColor('1bd8f4');
|
||||||
|
this.feeColors = this.mempoolFeeColors.map(hexToColor);
|
||||||
|
this.auditFeeColors = this.feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hexToColor(hex: string): Color {
|
||||||
|
return {
|
||||||
|
r: parseInt(hex.slice(0, 2), 16) / 255,
|
||||||
|
g: parseInt(hex.slice(2, 4), 16) / 255,
|
||||||
|
b: parseInt(hex.slice(4, 6), 16) / 255,
|
||||||
|
a: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desaturate(color: Color, amount: number): Color {
|
||||||
|
const gray = (color.r + color.g + color.b) / 6;
|
||||||
|
return {
|
||||||
|
r: color.r + ((gray - color.r) * amount),
|
||||||
|
g: color.g + ((gray - color.g) * amount),
|
||||||
|
b: color.b + ((gray - color.b) * amount),
|
||||||
|
a: color.a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function darken(color: Color, amount: number): Color {
|
||||||
|
return {
|
||||||
|
r: color.r * amount,
|
||||||
|
g: color.g * amount,
|
||||||
|
b: color.b * amount,
|
||||||
|
a: color.a,
|
||||||
|
}
|
||||||
|
}
|
70
frontend/src/theme-contrast.scss
Normal file
70
frontend/src/theme-contrast.scss
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/* Theme */
|
||||||
|
$bg: #ff1f31;
|
||||||
|
$active-bg: #ff131f;
|
||||||
|
$hover-bg: #ff131e;
|
||||||
|
$fg: #ff0;
|
||||||
|
|
||||||
|
/* Bootstrap */
|
||||||
|
|
||||||
|
$body-bg: $bg;
|
||||||
|
$body-color: $fg;
|
||||||
|
$gray-800: $bg;
|
||||||
|
$gray-700: $fg;
|
||||||
|
|
||||||
|
$nav-tabs-link-active-bg: $active-bg;
|
||||||
|
|
||||||
|
$primary: #105fb0;
|
||||||
|
$secondary: #2d3348;
|
||||||
|
$tertiary: #653b9c;
|
||||||
|
$success: #1a9436;
|
||||||
|
$info: #1bd8f4;
|
||||||
|
|
||||||
|
$h5-font-size: 1.15rem !default;
|
||||||
|
|
||||||
|
$pagination-bg: $body-bg;
|
||||||
|
$pagination-border-color: $gray-800;
|
||||||
|
$pagination-disabled-bg: $fg;
|
||||||
|
$pagination-disabled-border-color: $bg;
|
||||||
|
$pagination-active-color: $fg;
|
||||||
|
$pagination-active-bg: $tertiary;
|
||||||
|
$pagination-hover-bg: $hover-bg;
|
||||||
|
$pagination-hover-border-color: $bg;
|
||||||
|
$pagination-disabled-bg: $bg;
|
||||||
|
|
||||||
|
$custom-select-indicator-color: $fg;
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background-color: #1c2031 !important;
|
||||||
|
border: 1px solid #20263e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
$link-color: $info;
|
||||||
|
$link-decoration: none !default;
|
||||||
|
$link-hover-color: darken($link-color, 15%) !default;
|
||||||
|
$link-hover-decoration: underline !default;
|
||||||
|
|
||||||
|
$dropdown-bg: $bg;
|
||||||
|
$dropdown-link-color: $fg;
|
||||||
|
|
||||||
|
$dropdown-link-hover-color: $fg;
|
||||||
|
$dropdown-link-hover-bg: $active-bg;
|
||||||
|
|
||||||
|
$dropdown-link-active-color: $fg;
|
||||||
|
$dropdown-link-active-bg: $active-bg;
|
||||||
|
|
||||||
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #{$bg};
|
||||||
|
--active-bg: #{$active-bg};
|
||||||
|
--hover-bg: #{$hover-bg};
|
||||||
|
--fg: #{$fg};
|
||||||
|
|
||||||
|
--primary: #{$primary};
|
||||||
|
--secondary: #{$secondary};
|
||||||
|
--tertiary: #{$tertiary};
|
||||||
|
--success: #{$success};
|
||||||
|
--info: #{$info};
|
||||||
|
|
||||||
|
--box-bg: var(--box-bg);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user