mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 22:58:30 +01:00
Merge pull request #4321 from mempool/mononaut/ssr
Angular Universal SSR
This commit is contained in:
commit
b4d0b75557
94 changed files with 3390 additions and 300 deletions
|
@ -280,6 +280,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"server": {
|
||||||
|
"builder": "@angular-devkit/build-angular:server",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/mempool/server",
|
||||||
|
"main": "server.ts",
|
||||||
|
"tsConfig": "tsconfig.server.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"outputHashing": "media",
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sourceMap": false,
|
||||||
|
"localize": true,
|
||||||
|
"optimization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve-ssr": {
|
||||||
|
"builder": "@nguniversal/builders:ssr-dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "mempool:build",
|
||||||
|
"serverTarget": "mempool:server"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "mempool:build:production",
|
||||||
|
"serverTarget": "mempool:server:production",
|
||||||
|
"optimization": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prerender": {
|
||||||
|
"builder": "@nguniversal/builders:prerender",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "mempool:build:production",
|
||||||
|
"serverTarget": "mempool:server:production",
|
||||||
|
"routes": [
|
||||||
|
"/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cypress-run": {
|
"cypress-run": {
|
||||||
"builder": "@cypress/schematic:cypress",
|
"builder": "@cypress/schematic:cypress",
|
||||||
"options": {
|
"options": {
|
||||||
|
|
|
@ -71,7 +71,7 @@ const newConfig = `(function (window) {
|
||||||
window.__env.${obj.key} = ${typeof obj.value === 'string' ? `'${obj.value}'` : obj.value};`, '')}
|
window.__env.${obj.key} = ${typeof obj.value === 'string' ? `'${obj.value}'` : obj.value};`, '')}
|
||||||
window.__env.GIT_COMMIT_HASH = '${gitCommitHash}';
|
window.__env.GIT_COMMIT_HASH = '${gitCommitHash}';
|
||||||
window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}';
|
window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}';
|
||||||
}(this));`;
|
}((typeof global !== 'undefined') ? global : this));`;
|
||||||
|
|
||||||
const newConfigTemplate = `(function (window) {
|
const newConfigTemplate = `(function (window) {
|
||||||
window.__env = window.__env || {};${settings.reduce((str, obj) => `${str}
|
window.__env = window.__env || {};${settings.reduce((str, obj) => `${str}
|
||||||
|
|
2659
frontend/package-lock.json
generated
2659
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -48,6 +48,9 @@
|
||||||
"prettier": "prettier --write \"src/app/**/*.{js,json,css,scss,less,md,ts,html,component.html}\"",
|
"prettier": "prettier --write \"src/app/**/*.{js,json,css,scss,less,md,ts,html,component.html}\"",
|
||||||
"e2e": "npm run generate-config && npm run ng -- e2e",
|
"e2e": "npm run generate-config && npm run ng -- e2e",
|
||||||
"e2e:ci": "npm run cypress:run:ci",
|
"e2e:ci": "npm run cypress:run:ci",
|
||||||
|
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
||||||
|
"serve:ssr": "npm run generate-config && node server.run.js",
|
||||||
|
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
|
||||||
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
||||||
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config",
|
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config",
|
||||||
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
||||||
|
@ -61,18 +64,18 @@
|
||||||
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
|
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/build-angular": "^16.2.0",
|
"@angular-devkit/build-angular": "^16.1.1",
|
||||||
"@angular/animations": "^16.2.2",
|
"@angular/animations": "^16.1.1",
|
||||||
"@angular/cli": "^16.2.0",
|
"@angular/cli": "^16.1.1",
|
||||||
"@angular/common": "^16.2.2",
|
"@angular/common": "^16.1.1",
|
||||||
"@angular/compiler": "^16.2.2",
|
"@angular/compiler": "^16.1.1",
|
||||||
"@angular/core": "^16.2.2",
|
"@angular/core": "^16.1.1",
|
||||||
"@angular/forms": "^16.2.2",
|
"@angular/forms": "^16.1.1",
|
||||||
"@angular/localize": "^16.2.2",
|
"@angular/localize": "^16.1.1",
|
||||||
"@angular/platform-browser": "^16.2.2",
|
"@angular/platform-browser": "^16.1.1",
|
||||||
"@angular/platform-browser-dynamic": "^16.2.2",
|
"@angular/platform-browser-dynamic": "^16.1.1",
|
||||||
"@angular/platform-server": "^16.2.2",
|
"@angular/platform-server": "^16.1.1",
|
||||||
"@angular/router": "^16.2.2",
|
"@angular/router": "^16.1.1",
|
||||||
"@fortawesome/angular-fontawesome": "~0.13.0",
|
"@fortawesome/angular-fontawesome": "~0.13.0",
|
||||||
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
||||||
|
@ -96,14 +99,17 @@
|
||||||
"zone.js": "~0.13.1"
|
"zone.js": "~0.13.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^16.1.5",
|
"@angular/compiler-cli": "^16.1.1",
|
||||||
"@angular/language-service": "^16.1.5",
|
"@angular/language-service": "^16.1.1",
|
||||||
|
"@nguniversal/builders": "16.1.1",
|
||||||
|
"@nguniversal/express-engine": "16.1.1",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||||
"@typescript-eslint/parser": "^5.48.1",
|
"@typescript-eslint/parser": "^5.48.1",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
"http-proxy-middleware": "~2.0.6",
|
"http-proxy-middleware": "~2.0.6",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"typescript": "~4.9.3"
|
"typescript": "~4.9.3"
|
||||||
},
|
},
|
||||||
|
|
105
frontend/server.run.ts
Normal file
105
frontend/server.run.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'zone.js/dist/zone-node';
|
||||||
|
import './src/resources/config.js';
|
||||||
|
|
||||||
|
import * as domino from 'domino';
|
||||||
|
import * as express from 'express';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const {readFileSync, existsSync} = require('fs');
|
||||||
|
const {createProxyMiddleware} = require('http-proxy-middleware');
|
||||||
|
|
||||||
|
const template = fs.readFileSync(path.join(process.cwd(), 'dist/mempool/browser/en-US/', 'index.html')).toString();
|
||||||
|
const win = domino.createWindow(template);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
win.__env = global.__env;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
win.matchMedia = () => {
|
||||||
|
return {
|
||||||
|
matches: true
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// @ts-ignore
|
||||||
|
win.setTimeout = (fn) => { fn(); };
|
||||||
|
win.document.body.scrollTo = (() => {});
|
||||||
|
// @ts-ignore
|
||||||
|
global['window'] = win;
|
||||||
|
global['document'] = win.document;
|
||||||
|
// @ts-ignore
|
||||||
|
global['history'] = { state: { } };
|
||||||
|
|
||||||
|
global['localStorage'] = {
|
||||||
|
getItem: () => '',
|
||||||
|
setItem: () => {},
|
||||||
|
removeItem: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
length: 0,
|
||||||
|
key: () => '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of supported and actually active locales
|
||||||
|
*/
|
||||||
|
function getActiveLocales() {
|
||||||
|
const angularConfig = JSON.parse(readFileSync('angular.json', 'utf8'));
|
||||||
|
|
||||||
|
const supportedLocales = [
|
||||||
|
angularConfig.projects.mempool.i18n.sourceLocale,
|
||||||
|
...Object.keys(angularConfig.projects.mempool.i18n.locales),
|
||||||
|
];
|
||||||
|
|
||||||
|
return supportedLocales.filter(locale => locale === 'en-US' && existsSync(`./dist/mempool/server/${locale}`));
|
||||||
|
// return supportedLocales.filter(locale => existsSync(`./dist/mempool/server/${locale}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function app() {
|
||||||
|
const server = express();
|
||||||
|
|
||||||
|
// proxy websocket
|
||||||
|
server.get('/api/v1/ws', createProxyMiddleware({
|
||||||
|
target: 'ws://localhost:4200/api/v1/ws',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
logLevel: 'debug'
|
||||||
|
}));
|
||||||
|
// proxy API to nginx
|
||||||
|
server.get('/api/**', createProxyMiddleware({
|
||||||
|
// @ts-ignore
|
||||||
|
target: win.__env.NGINX_PROTOCOL + '://' + win.__env.NGINX_HOSTNAME + ':' + win.__env.NGINX_PORT,
|
||||||
|
changeOrigin: true,
|
||||||
|
}));
|
||||||
|
server.get('/resources/**', express.static('./src'));
|
||||||
|
|
||||||
|
|
||||||
|
// map / and /en to en-US
|
||||||
|
const defaultLocale = 'en-US';
|
||||||
|
console.log(`serving default locale: ${defaultLocale}`);
|
||||||
|
const appServerModule = require(`./dist/mempool/server/${defaultLocale}/main.js`);
|
||||||
|
server.use('/', appServerModule.app(defaultLocale));
|
||||||
|
server.use('/en', appServerModule.app(defaultLocale));
|
||||||
|
|
||||||
|
// map each locale to its localized main.js
|
||||||
|
getActiveLocales().forEach(locale => {
|
||||||
|
console.log('serving locale:', locale);
|
||||||
|
const appServerModule = require(`./dist/mempool/server/${locale}/main.js`);
|
||||||
|
|
||||||
|
// map everything to itself
|
||||||
|
server.use(`/${locale}`, appServerModule.app(locale));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
const port = process.env.PORT || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
app().listen(port, () => {
|
||||||
|
console.log(`Node Express server listening on port ${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
112
frontend/server.ts
Normal file
112
frontend/server.ts
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import 'zone.js/dist/zone-node';
|
||||||
|
import './src/resources/config.js';
|
||||||
|
|
||||||
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
|
import * as express from 'express';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as domino from 'domino';
|
||||||
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
|
|
||||||
|
import { join } from 'path';
|
||||||
|
import { AppServerModule } from './src/main.server';
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
import { ResizeObserver } from './shims';
|
||||||
|
|
||||||
|
const template = fs.readFileSync(path.join(process.cwd(), 'dist/mempool/browser/en-US/', 'index.html')).toString();
|
||||||
|
const win = domino.createWindow(template);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
win.__env = global.__env;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
win.matchMedia = (media) => {
|
||||||
|
return {
|
||||||
|
media,
|
||||||
|
matches: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
win.setTimeout = (fn) => { fn(); };
|
||||||
|
win.document.body.scrollTo = (() => {});
|
||||||
|
win['ResizeObserver'] = ResizeObserver;
|
||||||
|
// @ts-ignore
|
||||||
|
global['window'] = win;
|
||||||
|
// @ts-ignore
|
||||||
|
global['document'] = win.document;
|
||||||
|
// @ts-ignore
|
||||||
|
global['history'] = { state: { } };
|
||||||
|
// @ts-ignore
|
||||||
|
Object.defineProperty(global, 'navigator', {
|
||||||
|
value: win.navigator,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
global['localStorage'] = {
|
||||||
|
getItem: () => '',
|
||||||
|
setItem: () => {},
|
||||||
|
removeItem: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
length: 0,
|
||||||
|
key: () => '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(locale: string): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const distFolder = join(process.cwd(), `dist/mempool/browser/${locale}`);
|
||||||
|
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
|
||||||
|
|
||||||
|
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||||
|
server.engine('html', ngExpressEngine({
|
||||||
|
bootstrap: AppServerModule,
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', distFolder);
|
||||||
|
|
||||||
|
|
||||||
|
// static file handler so we send HTTP 404 to nginx
|
||||||
|
server.get('/**.(css|js|json|ico|webmanifest|png|jpg|jpeg|svg|mp4)*', express.static(distFolder, { maxAge: '1y', fallthrough: false }));
|
||||||
|
// handle page routes
|
||||||
|
server.get('/**', getLocalizedSSR(indexHtml));
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedSSR(indexHtml) {
|
||||||
|
return (req, res) => {
|
||||||
|
res.render(indexHtml, {
|
||||||
|
req,
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_BASE_HREF, useValue: req.baseUrl }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only used for development mode
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env.PORT || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app('en-US');
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Node Express server listening on port ${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webpack will replace 'require' with '__webpack_require__'
|
||||||
|
// '__non_webpack_require__' is a proxy to Node 'require'
|
||||||
|
// The below code is to ensure that the server is run only when not requiring the bundle.
|
||||||
|
declare const __non_webpack_require__: NodeRequire;
|
||||||
|
const mainModule = __non_webpack_require__.main;
|
||||||
|
const moduleFilename = mainModule && mainModule.filename || '';
|
||||||
|
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from './src/main.server';
|
7
frontend/shims.ts
Normal file
7
frontend/shims.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export class ResizeObserver {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
disconnect() {}
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ZONE_SERVICE } from './injection-tokens';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './components/app/app.component';
|
import { AppComponent } from './components/app/app.component';
|
||||||
import { ElectrsApiService } from './services/electrs-api.service';
|
import { ElectrsApiService } from './services/electrs-api.service';
|
||||||
|
@ -13,6 +14,7 @@ import { WebsocketService } from './services/websocket.service';
|
||||||
import { AudioService } from './services/audio.service';
|
import { AudioService } from './services/audio.service';
|
||||||
import { SeoService } from './services/seo.service';
|
import { SeoService } from './services/seo.service';
|
||||||
import { OpenGraphService } from './services/opengraph.service';
|
import { OpenGraphService } from './services/opengraph.service';
|
||||||
|
import { ZoneService } from './services/zone-shim.service';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
|
@ -42,7 +44,8 @@ const providers = [
|
||||||
CapAddressPipe,
|
CapAddressPipe,
|
||||||
AppPreloadingStrategy,
|
AppPreloadingStrategy,
|
||||||
ServicesApiServices,
|
ServicesApiServices,
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
|
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true },
|
||||||
|
{ provide: ZONE_SERVICE, useClass: ZoneService },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
|
import { ServerModule } from '@angular/platform-server';
|
||||||
|
|
||||||
|
import { ZONE_SERVICE } from './injection-tokens';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { AppComponent } from './components/app/app.component';
|
import { AppComponent } from './components/app/app.component';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
|
import { ZoneService } from './services/zone.service';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
AppModule,
|
AppModule,
|
||||||
ServerModule,
|
ServerModule,
|
||||||
ServerTransferStateModule,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
|
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true },
|
||||||
|
{ provide: ZONE_SERVICE, useClass: ZoneService },
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class.chart]="!widget" [class.chart-widget]="widget" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { EChartsOption } from 'echarts';
|
import { EChartsOption } from '../../../graphs/echarts';
|
||||||
import { Observable, Subscription, combineLatest, fromEvent, share } from 'rxjs';
|
import { Observable, Subscription, combineLatest, fromEvent, share } from 'rxjs';
|
||||||
import { startWith, switchMap, tap } from 'rxjs/operators';
|
import { startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
|
@ -11,6 +11,7 @@ import { MiningService } from '../../../services/mining.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
|
import { StateService } from '../../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-acceleration-fees-graph',
|
selector: 'app-acceleration-fees-graph',
|
||||||
|
@ -59,6 +60,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
public stateService: StateService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
@ -176,10 +178,10 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||||
padding: [10, 0, 0, 0],
|
padding: [10, 0, 0, 0],
|
||||||
},
|
},
|
||||||
type: 'time',
|
type: 'time',
|
||||||
boundaryGap: false,
|
boundaryGap: [0, 0],
|
||||||
axisLine: { onZero: true },
|
axisLine: { onZero: true },
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: val => formatterXAxisTimeCategory(this.locale, this.timespan, parseInt(val, 10)),
|
formatter: (val): string => formatterXAxisTimeCategory(this.locale, this.timespan, val),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
lineHeight: 12,
|
lineHeight: 12,
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="mempool-block-wrapper">
|
<div class="mempool-block-wrapper" *ngIf="webGlEnabled">
|
||||||
<app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview>
|
<app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,16 +66,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Avg block fees graph -->
|
|
||||||
<!-- <div class="col" style="margin-bottom: 1.47rem">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
|
||||||
<app-block-fee-rates-graph [attr.data-cy]="'hashrate-graph'" [widget]="true"></app-block-fee-rates-graph>
|
|
||||||
<div class="mt-1"><a [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" fragment="1m" i18n="dashboard.view-more">View more »</a></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- Recent Accelerations List -->
|
<!-- Recent Accelerations List -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card list-card">
|
<div class="card list-card">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
import { OpenGraphService } from '../../../services/opengraph.service';
|
import { OpenGraphService } from '../../../services/opengraph.service';
|
||||||
import { WebsocketService } from '../../../services/websocket.service';
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
|
@ -10,6 +10,7 @@ import { hexToColor } from '../../block-overview-graph/utils';
|
||||||
import TxView from '../../block-overview-graph/tx-view';
|
import TxView from '../../block-overview-graph/tx-view';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../../app.constants';
|
import { feeLevels, mempoolFeeColors } from '../../../app.constants';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
|
import { detectWebGL } from '../../../shared/graphs.utils';
|
||||||
|
|
||||||
const acceleratedColor: Color = hexToColor('8F5FF6');
|
const acceleratedColor: Color = hexToColor('8F5FF6');
|
||||||
const normalColors = mempoolFeeColors.map(hex => hexToColor(hex + '5F'));
|
const normalColors = mempoolFeeColors.map(hex => hexToColor(hex + '5F'));
|
||||||
|
@ -30,6 +31,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||||
pendingAccelerations$: Observable<Acceleration[]>;
|
pendingAccelerations$: Observable<Acceleration[]>;
|
||||||
minedAccelerations$: Observable<Acceleration[]>;
|
minedAccelerations$: Observable<Acceleration[]>;
|
||||||
loadingBlocks: boolean = true;
|
loadingBlocks: boolean = true;
|
||||||
|
webGlEnabled = true;
|
||||||
|
|
||||||
graphHeight: number = 300;
|
graphHeight: number = 300;
|
||||||
|
|
||||||
|
@ -39,7 +41,9 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private serviceApiServices: ServicesApiServices,
|
private serviceApiServices: ServicesApiServices,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`);
|
this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`);
|
||||||
this.ogService.setManualOgImage('accelerator.jpg');
|
this.ogService.setManualOgImage('accelerator.jpg');
|
||||||
}
|
}
|
||||||
|
@ -48,7 +52,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||||
this.onResize();
|
this.onResize();
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
|
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
|
||||||
|
|
||||||
this.pendingAccelerations$ = interval(30000).pipe(
|
this.pendingAccelerations$ = (this.stateService.isBrowser ? interval(30000) : of(null)).pipe(
|
||||||
startWith(true),
|
startWith(true),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
return this.serviceApiServices.getAccelerations$().pipe(
|
return this.serviceApiServices.getAccelerations$().pipe(
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!error">
|
<ng-container *ngIf="!error">
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||||
|
|
|
@ -62,10 +62,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class.chart]="!widget" [class.chart-widget]="widget" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
|
||||||
import { EChartsOption, graphic } from 'echarts';
|
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||||
import { Observable, combineLatest, of } from 'rxjs';
|
import { Observable, combineLatest, of } from 'rxjs';
|
||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
|
@ -55,7 +55,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -209,7 +209,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||||
|
|
||||||
prepareChartOptions(data, weightMode) {
|
prepareChartOptions(data, weightMode) {
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
color: this.widget ? ['#6b6b6b', new graphic.LinearGradient(0, 0, 0, 0.65, [
|
color: this.widget ? ['#6b6b6b', new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||||
{ offset: 0, color: '#F4511E' },
|
{ offset: 0, color: '#F4511E' },
|
||||||
{ offset: 0.25, color: '#FB8C00' },
|
{ offset: 0.25, color: '#FB8C00' },
|
||||||
{ offset: 0.5, color: '#FFB300' },
|
{ offset: 0.5, color: '#FFB300' },
|
||||||
|
@ -282,7 +282,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||||
legend: (this.widget || data.series.length === 0) ? undefined : {
|
legend: (this.widget || data.series.length === 0) ? undefined : {
|
||||||
padding: [10, 75],
|
padding: [10, 75],
|
||||||
data: data.legends,
|
data: data.legends,
|
||||||
selected: JSON.parse(this.storageService.getValue('fee_rates_legend')) ?? {
|
selected: JSON.parse(this.storageService.getValue('fee_rates_legend') || 'null') ?? {
|
||||||
'Min': true,
|
'Min': true,
|
||||||
'10th': true,
|
'10th': true,
|
||||||
'25th': true,
|
'25th': true,
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { MiningService } from '../../services/mining.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||||
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-fees-graph',
|
selector: 'app-block-fees-graph',
|
||||||
|
@ -54,6 +55,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
public stateService: StateService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private fiatShortenerPipe: FiatShortenerPipe,
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
private fiatCurrencyPipe: FiatCurrencyPipe,
|
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) {
|
) {
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
<div class="grid-align" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'">
|
<div class="grid-align" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'">
|
||||||
<div class="block-overview-graph">
|
<div class="block-overview-graph">
|
||||||
<canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
|
<canvas *browserOnly class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
|
||||||
<div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">
|
<div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">
|
||||||
<div *ngIf="!unavailable" class="spinner-border ml-3 loading" role="status"></div>
|
<div *ngIf="!unavailable" class="spinner-border ml-3 loading" role="status"></div>
|
||||||
<div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div>
|
<div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div>
|
||||||
|
|
|
@ -83,9 +83,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
constructor(
|
constructor(
|
||||||
readonly ngZone: NgZone,
|
readonly ngZone: NgZone,
|
||||||
readonly elRef: ElementRef,
|
readonly elRef: ElementRef,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||||
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
|
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
|
||||||
this.searchText = text;
|
this.searchText = text;
|
||||||
|
@ -94,6 +94,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
if (this.canvas) {
|
||||||
this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false);
|
this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false);
|
||||||
this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);
|
this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);
|
||||||
this.gl = this.canvas.nativeElement.getContext('webgl');
|
this.gl = this.canvas.nativeElement.getContext('webgl');
|
||||||
|
@ -103,6 +104,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
if (changes.orientation || changes.flip) {
|
if (changes.orientation || changes.flip) {
|
||||||
|
@ -142,9 +144,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
cancelAnimationFrame(this.animationFrameRequest);
|
cancelAnimationFrame(this.animationFrameRequest);
|
||||||
clearTimeout(this.animationHeartBeat);
|
clearTimeout(this.animationHeartBeat);
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clear(direction): void {
|
clear(direction): void {
|
||||||
this.exit(direction);
|
this.exit(direction);
|
||||||
|
@ -209,6 +213,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
}
|
}
|
||||||
|
|
||||||
initCanvas(): void {
|
initCanvas(): void {
|
||||||
|
if (!this.canvas || !this.gl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
@ -262,6 +270,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
resizeCanvas(): void {
|
resizeCanvas(): void {
|
||||||
|
if (this.canvas) {
|
||||||
this.cssWidth = this.canvas.nativeElement.offsetParent.clientWidth;
|
this.cssWidth = this.canvas.nativeElement.offsetParent.clientWidth;
|
||||||
this.cssHeight = this.canvas.nativeElement.offsetParent.clientHeight;
|
this.cssHeight = this.canvas.nativeElement.offsetParent.clientHeight;
|
||||||
this.displayWidth = window.devicePixelRatio * this.cssWidth;
|
this.displayWidth = window.devicePixelRatio * this.cssWidth;
|
||||||
|
@ -282,6 +291,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileShader(src, type): WebGLShader {
|
compileShader(src, type): WebGLShader {
|
||||||
if (!this.gl) {
|
if (!this.gl) {
|
||||||
|
@ -406,6 +416,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
|
|
||||||
@HostListener('pointerup', ['$event'])
|
@HostListener('pointerup', ['$event'])
|
||||||
onClick(event) {
|
onClick(event) {
|
||||||
|
if (!this.canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') {
|
if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') {
|
||||||
this.setPreviewTx(event.offsetX, event.offsetY, true);
|
this.setPreviewTx(event.offsetX, event.offsetY, true);
|
||||||
} else if (event.target === this.canvas.nativeElement) {
|
} else if (event.target === this.canvas.nativeElement) {
|
||||||
|
@ -417,6 +430,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||||
|
|
||||||
@HostListener('pointermove', ['$event'])
|
@HostListener('pointermove', ['$event'])
|
||||||
onPointerMove(event) {
|
onPointerMove(event) {
|
||||||
|
if (!this.canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.target === this.canvas.nativeElement) {
|
if (event.target === this.canvas.nativeElement) {
|
||||||
this.setPreviewTx(event.offsetX, event.offsetY, false);
|
this.setPreviewTx(event.offsetX, event.offsetY, false);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -37,10 +37,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { StorageService } from '../../services/storage.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||||
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-rewards-graph',
|
selector: 'app-block-rewards-graph',
|
||||||
|
@ -54,6 +55,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
|
public stateService: StateService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private fiatShortenerPipe: FiatShortenerPipe,
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
private fiatCurrencyPipe: FiatCurrencyPipe,
|
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||||
|
|
|
@ -44,10 +44,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { StorageService } from '../../services/storage.service';
|
||||||
import { MiningService } from '../../services/mining.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { download, formatterXAxis } from '../../shared/graphs.utils';
|
import { download, formatterXAxis } from '../../shared/graphs.utils';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-sizes-weights-graph',
|
selector: 'app-block-sizes-weights-graph',
|
||||||
|
@ -52,6 +53,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
public stateService: StateService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<div class="col-sm chart-container" *ngIf="webGlEnabled && !showAudit">
|
<div class="col-sm chart-container" *ngIf="webGlEnabled && !showAudit">
|
||||||
<app-block-overview-graph
|
<app-block-overview-graph
|
||||||
#blockGraphActual
|
#blockGraphActual
|
||||||
[isLoading]="isLoadingOverview"
|
[isLoading]="!stateService.isBrowser || isLoadingOverview"
|
||||||
[resolution]="86"
|
[resolution]="86"
|
||||||
[blockLimit]="stateService.blockVSize"
|
[blockLimit]="stateService.blockVSize"
|
||||||
[orientation]="'top'"
|
[orientation]="'top'"
|
||||||
|
@ -229,7 +229,7 @@
|
||||||
<div class="col-sm audit-col" [class.mobile]="isMobile">
|
<div class="col-sm audit-col" [class.mobile]="isMobile">
|
||||||
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container></h3>
|
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container></h3>
|
||||||
<div class="block-graph-wrapper">
|
<div class="block-graph-wrapper">
|
||||||
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="86"
|
<app-block-overview-graph #blockGraphProjected [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
||||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
|
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
|
||||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"
|
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"
|
||||||
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
||||||
|
@ -244,7 +244,7 @@
|
||||||
<div class="col-sm audit-col" *ngIf="!isMobile">
|
<div class="col-sm audit-col" *ngIf="!isMobile">
|
||||||
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container><a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
|
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container><a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
|
||||||
<div class="block-graph-wrapper">
|
<div class="block-graph-wrapper">
|
||||||
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="86"
|
<app-block-overview-graph #blockGraphActual [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
||||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
|
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
|
||||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"
|
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"
|
||||||
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, Inject, PLATFORM_ID, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
|
@ -108,8 +108,10 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||||
private priceService: PriceService,
|
private priceService: PriceService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -318,6 +320,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
this.transactions = transactions;
|
this.transactions = transactions;
|
||||||
this.isLoadingTransactions = false;
|
this.isLoadingTransactions = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
@ -471,6 +474,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.isLoadingOverview = false;
|
this.isLoadingOverview = false;
|
||||||
this.setupBlockGraphs();
|
this.setupBlockGraphs();
|
||||||
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.oobSubscription = block$.pipe(
|
this.oobSubscription = block$.pipe(
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<div *ngIf="indexingAvailable" class="tooltip-custom">
|
<div *ngIf="indexingAvailable" class="tooltip-custom">
|
||||||
<a class="clear-link" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]">
|
<a class="clear-link" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]">
|
||||||
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
|
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
|
||||||
onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||||
<span class="pool-name">{{ block.extras.pool.name }}</span>
|
<span class="pool-name">{{ block.extras.pool.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
<span *ngIf="!widget" class="tooltiptext badge badge-secondary scriptmessage">{{ block.extras.coinbaseRaw | hex2ascii }}</span>
|
<span *ngIf="!widget" class="tooltiptext badge badge-secondary scriptmessage">{{ block.extras.coinbaseRaw | hex2ascii }}</span>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Subscription, tap, timer } from 'rxjs';
|
import { Subscription, tap, timer } from 'rxjs';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -33,8 +32,10 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.stateService.isBrowser) {
|
||||||
this.timeSubscription = timer(0, 250).pipe(
|
this.timeSubscription = timer(0, 250).pipe(
|
||||||
tap(() => {
|
tap(() => {
|
||||||
|
console.log('face tick');
|
||||||
this.updateTime();
|
this.updateTime();
|
||||||
})
|
})
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
@ -45,6 +46,7 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.updateSegments();
|
this.updateSegments();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.faceStyle = {
|
this.faceStyle = {
|
||||||
|
@ -54,8 +56,10 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
if (this.timeSubscription) {
|
||||||
this.timeSubscription.unsubscribe();
|
this.timeSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateTime(): void {
|
updateTime(): void {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
|
@ -110,8 +110,8 @@ export class ClockComponent implements OnInit {
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
resizeCanvas(): void {
|
resizeCanvas(): void {
|
||||||
const windowWidth = this.limitWidth || window.innerWidth;
|
const windowWidth = this.limitWidth || window.innerWidth || 800;
|
||||||
const windowHeight = this.limitHeight || window.innerHeight;
|
const windowHeight = this.limitHeight || window.innerHeight || 800;
|
||||||
this.chainWidth = windowWidth;
|
this.chainWidth = windowWidth;
|
||||||
this.chainHeight = Math.max(60, windowHeight / 8);
|
this.chainHeight = Math.max(60, windowHeight / 8);
|
||||||
this.clockSize = Math.min(800, windowWidth, windowHeight - (1.4 * this.chainHeight));
|
this.clockSize = Math.min(800, windowWidth, windowHeight - (1.4 * this.chainHeight));
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private bytesPipe: BytesPipe,
|
private bytesPipe: BytesPipe,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees">
|
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions && stateService.isBrowser; else loadingFees">
|
||||||
<div echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
|
<div *browserOnly echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #loadingFees>
|
<ng-template #loadingFees>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private vbytesPipe: VbytesPipe,
|
private vbytesPipe: VbytesPipe,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class]="!widget ? 'chart' : 'chart-widget'" [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class HashrateChartComponent implements OnInit {
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private stateService: StateService
|
public stateService: StateService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ export class HashrateChartComponent implements OnInit {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected: JSON.parse(this.storageService.getValue('hashrate_difficulty_legend')) ?? {
|
selected: JSON.parse(this.storageService?.getValue('hashrate_difficulty_legend') || 'null') ?? {
|
||||||
'$localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`': true,
|
'$localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`': true,
|
||||||
'$localize`::Difficulty`': this.network === '',
|
'$localize`::Difficulty`': this.network === '',
|
||||||
'$localize`Hashrate (MA)`': true,
|
'$localize`Hashrate (MA)`': true,
|
||||||
|
|
|
@ -31,10 +31,10 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { StorageService } from '../../services/storage.service';
|
||||||
import { MiningService } from '../../services/mining.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
import { download } from '../../shared/graphs.utils';
|
import { download } from '../../shared/graphs.utils';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
interface Hashrate {
|
interface Hashrate {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
@ -60,6 +61,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
public stateService: StateService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
|
<div class="echarts" *browserOnly echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
|
@ -48,7 +48,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="echarts" echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
|
<div class="echarts" *browserOnly echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
|
@ -1,6 +1,7 @@
|
||||||
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
|
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
|
||||||
import { formatDate, formatNumber } from '@angular/common';
|
import { formatDate, formatNumber } from '@angular/common';
|
||||||
import { EChartsOption } from '../../graphs/echarts';
|
import { EChartsOption } from '../../graphs/echarts';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-lbtc-pegs-graph',
|
selector: 'app-lbtc-pegs-graph',
|
||||||
|
@ -32,6 +33,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
public stateService: StateService,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="echarts" echarts [initOpts]="ratioChartInitOptions" [options]="ratioChartOptions" (chartRendered)="rendered()"></div>
|
<div class="echarts" *browserOnly echarts [initOpts]="ratioChartInitOptions" [options]="ratioChartOptions" (chartRendered)="rendered()"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<app-block-overview-graph
|
<app-block-overview-graph
|
||||||
#blockGraph
|
#blockGraph
|
||||||
[isLoading]="isLoading$ | async"
|
[isLoading]="(isLoading$ | async) || !stateService.isBrowser"
|
||||||
[resolution]="resolution"
|
[resolution]="resolution"
|
||||||
[blockLimit]="stateService.blockVSize"
|
[blockLimit]="stateService.blockVSize"
|
||||||
[orientation]="timeLtr ? 'right' : 'left'"
|
[orientation]="timeLtr ? 'right' : 'left'"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Inject, PLATFORM_ID, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
||||||
|
@ -29,8 +30,9 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -93,9 +95,3 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||||
this.previewTx = event;
|
this.previewTx = event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectWebGL() {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
||||||
return (gl && gl instanceof WebGLRenderingContext);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
|
<div *browserOnly echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
|
@ -59,7 +59,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
private vbytesPipe: VbytesPipe,
|
private vbytesPipe: VbytesPipe,
|
||||||
private wubytesPipe: WuBytesPipe,
|
private wubytesPipe: WuBytesPipe,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
) { }
|
) { }
|
||||||
|
|
|
@ -76,11 +76,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
|
<div [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
|
||||||
<div [class]="widget ? 'chart-widget' : 'chart'" [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class]="widget ? 'chart-widget' : 'chart'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
<tr *ngFor="let pool of miningStats.pools">
|
<tr *ngFor="let pool of miningStats.pools">
|
||||||
<td class="d-none d-md-table-cell">{{ pool.rank }}</td>
|
<td class="d-none d-md-table-cell">{{ pool.rank }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
|
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'">
|
||||||
</td>
|
</td>
|
||||||
<td class="pool-name"><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
<td class="pool-name"><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
||||||
<td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{
|
<td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{
|
||||||
|
|
|
@ -41,7 +41,7 @@ export class PoolRankingComponent implements OnInit {
|
||||||
miningStatsObservable$: Observable<MiningStats>;
|
miningStatsObservable$: Observable<MiningStats>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row hash-chart full-width-row">
|
<div class="row hash-chart full-width-row">
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartFinished)="onChartReady()"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div *ngIf="poolStats$ | async as poolStats; else loadingMain">
|
<div *ngIf="poolStats$ | async as poolStats; else loadingMain">
|
||||||
<div style="display:flex" class="mb-3">
|
<div style="display:flex" class="mb-3">
|
||||||
<img width="50" height="50" src="{{ poolStats['logo'] }}" [alt]="poolStats.pool.name + ' mining pool logo'"
|
<img width="50" height="50" src="{{ poolStats['logo'] }}" [alt]="poolStats.pool.name + ' mining pool logo'"
|
||||||
onError="this.src = '/resources/mining-pools/default.svg'" class="mr-3">
|
onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'" class="mr-3">
|
||||||
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -168,8 +168,8 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!-- Hashrate chart -->
|
<!-- Hashrate chart -->
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -35,4 +35,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *serverOnly>
|
||||||
|
<!-- disgusting hack to apply an initial scroll to server-side rendered blockchain bar -->
|
||||||
|
<img *ngIf="!stateService.isLiquid()" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="pixel" style="visibility: hidden; position: absolute;" onload="(() => { const b = document.getElementById('blockchain-container'); const d = document.getElementById('divider'); if (b && d) { b.scrollLeft = d.getBoundingClientRect().x - (window.innerWidth * (window.innerWidth >= 768 ? 0.5 : 0.95)); }})()">
|
||||||
|
<img *ngIf="stateService.isLiquid()" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="pixel" style="visibility: hidden; position: absolute;" onload="(() => { const b = document.getElementById('blockchain-container'); const d = document.getElementById('divider'); if (b && d) { b.scrollLeft = d.getBoundingClientRect().x - (window.innerWidth >= 768 ? 420 : (window.innerWidth * 0.5)); }})()">
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy {
|
||||||
hasMenu = false;
|
hasMenu = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform);
|
this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef, Inject, ChangeDetectorRef } from '@angular/core';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import {
|
import {
|
||||||
|
@ -28,6 +28,7 @@ import { Price, PriceService } from '../../services/price.service';
|
||||||
import { isFeatureActive } from '../../bitcoin.utils';
|
import { isFeatureActive } from '../../bitcoin.utils';
|
||||||
import { ServicesApiServices } from '../../services/services-api.service';
|
import { ServicesApiServices } from '../../services/services-api.service';
|
||||||
import { EnterpriseService } from '../../services/enterprise.service';
|
import { EnterpriseService } from '../../services/enterprise.service';
|
||||||
|
import { ZONE_SERVICE } from '../../injection-tokens';
|
||||||
|
|
||||||
interface Pool {
|
interface Pool {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -101,7 +102,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
inputIndex: number;
|
inputIndex: number;
|
||||||
outputIndex: number;
|
outputIndex: number;
|
||||||
graphExpanded: boolean = false;
|
graphExpanded: boolean = false;
|
||||||
graphWidth: number = 1000;
|
graphWidth: number = 1068;
|
||||||
graphHeight: number = 360;
|
graphHeight: number = 360;
|
||||||
inOutLimit: number = 150;
|
inOutLimit: number = 150;
|
||||||
maxInOut: number = 0;
|
maxInOut: number = 0;
|
||||||
|
@ -141,6 +142,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
private priceService: PriceService,
|
private priceService: PriceService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private enterpriseService: EnterpriseService,
|
private enterpriseService: EnterpriseService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
@Inject(ZONE_SERVICE) private zoneService: any,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -356,7 +359,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subscription = this.route.paramMap
|
this.subscription = this.zoneService.wrapObservable(this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
const urlMatch = (params.get('id') || '').split(':');
|
const urlMatch = (params.get('id') || '').split(':');
|
||||||
|
@ -430,7 +433,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
return of(tx);
|
return of(tx);
|
||||||
})
|
})
|
||||||
)
|
))
|
||||||
.subscribe((tx: Transaction) => {
|
.subscribe((tx: Transaction) => {
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
this.fetchCachedTx$.next(this.txId);
|
this.fetchCachedTx$.next(this.txId);
|
||||||
|
@ -503,6 +506,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
|
||||||
setTimeout(() => { this.applyFragment(); }, 0);
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
|
|
||||||
|
this.cd.detectChanges();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
@ -785,9 +790,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
setGraphSize(): void {
|
setGraphSize(): void {
|
||||||
this.isMobile = window.innerWidth < 850;
|
this.isMobile = window.innerWidth < 850;
|
||||||
if (this.graphContainer?.nativeElement) {
|
if (this.graphContainer?.nativeElement && this.stateService.isBrowser) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.graphContainer?.nativeElement) {
|
if (this.graphContainer?.nativeElement?.clientWidth) {
|
||||||
this.graphWidth = this.graphContainer.nativeElement.clientWidth;
|
this.graphWidth = this.graphContainer.nativeElement.clientWidth;
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => { this.setGraphSize(); }, 1);
|
setTimeout(() => { this.setGraphSize(); }, 1);
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<div class="bowtie-graph">
|
<div class="bowtie-graph">
|
||||||
<svg *ngIf="inputs && outputs" class="bowtie" [class.rtl]="dir === 'rtl'" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
|
<svg
|
||||||
|
*ngIf="inputs && outputs"
|
||||||
|
class="bowtie"
|
||||||
|
[class.rtl]="dir === 'rtl'"
|
||||||
|
[attr.height]="(height + 10) + 'px'"
|
||||||
|
[attr.width]="stateService.isBrowser ? (width + 'px') : '100%'"
|
||||||
|
[attr.viewBox]="stateService.isBrowser ? null : ('0 0 ' + width + ' ' + (height + 10))"
|
||||||
|
[attr.preserveAspectRatio]="stateService.isBrowser ? null : 'none'"
|
||||||
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="input-arrow" viewBox="-5 -5 10 10"
|
<marker id="input-arrow" viewBox="-5 -5 10 10"
|
||||||
refX="0" refY="0"
|
refX="0" refY="0"
|
||||||
|
|
|
@ -101,7 +101,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
<div class="container-xl dashboard-container" *ngIf="(network$ | async) !== 'liquid'; else liquidDashboard">
|
<div class="container-xl dashboard-container" *ngIf="(network$ | async) !== 'liquid'; else liquidDashboard">
|
||||||
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
|
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
|
||||||
<ng-container *ngIf="(network$ | async) !== 'liquidtestnet'">
|
<ng-container>
|
||||||
<div class="col card-wrapper">
|
<div class="col card-wrapper">
|
||||||
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
|
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mempool-block-wrapper">
|
<div class="mempool-block-wrapper" *ngIf="webGlEnabled">
|
||||||
<app-mempool-block-overview
|
<app-mempool-block-overview
|
||||||
[index]="0"
|
[index]="0"
|
||||||
[resolution]="goggleResolution"
|
[resolution]="goggleResolution"
|
||||||
|
@ -45,12 +45,12 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||||
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
|
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
|
||||||
<div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats">
|
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
|
||||||
<app-incoming-transactions-graph
|
<app-incoming-transactions-graph
|
||||||
[height]="incomingGraphHeight"
|
[height]="incomingGraphHeight"
|
||||||
[left]="50"
|
[left]="50"
|
||||||
[right]="20"
|
[right]="20"
|
||||||
[data]="mempoolStats.value?.weightPerSecond"
|
[data]="mempoolStats?.weightPerSecond"
|
||||||
[windowPreferenceOverride]="'2h'"
|
[windowPreferenceOverride]="'2h'"
|
||||||
></app-incoming-transactions-graph>
|
></app-incoming-transactions-graph>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
||||||
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
||||||
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
|
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
|
||||||
|
@ -8,6 +8,7 @@ import { StateService } from '../services/state.service';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
import { SeoService } from '../services/seo.service';
|
import { SeoService } from '../services/seo.service';
|
||||||
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
|
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
|
||||||
|
import { detectWebGL } from '../shared/graphs.utils';
|
||||||
|
|
||||||
interface MempoolBlocksData {
|
interface MempoolBlocksData {
|
||||||
blocks: number;
|
blocks: number;
|
||||||
|
@ -67,6 +68,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
currency: string;
|
currency: string;
|
||||||
incomingGraphHeight: number = 300;
|
incomingGraphHeight: number = 300;
|
||||||
lbtcPegGraphHeight: number = 360;
|
lbtcPegGraphHeight: number = 360;
|
||||||
|
webGlEnabled = true;
|
||||||
private lastPegBlockUpdate: number = 0;
|
private lastPegBlockUpdate: number = 0;
|
||||||
private lastPegAmount: string = '';
|
private lastPegAmount: string = '';
|
||||||
private lastReservesBlockUpdate: number = 0;
|
private lastReservesBlockUpdate: number = 0;
|
||||||
|
@ -88,8 +90,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private seoService: SeoService
|
private seoService: SeoService,
|
||||||
) { }
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
|
) {
|
||||||
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.stateService.focusSearchInputDesktop();
|
this.stateService.focusSearchInputDesktop();
|
||||||
|
@ -241,7 +246,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
share(),
|
shareReplay(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.stateService.network === 'liquid') {
|
if (this.stateService.network === 'liquid') {
|
||||||
|
|
3
frontend/src/app/injection-tokens.ts
Normal file
3
frontend/src/app/injection-tokens.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const ZONE_SERVICE = new InjectionToken('ZONE_TASK');
|
|
@ -8,6 +8,7 @@ import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesP
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class LightningApiService {
|
export class LightningApiService {
|
||||||
|
private apiBaseUrl: string; // base URL is protocol, hostname, and port
|
||||||
private apiBasePath = ''; // network path is /testnet, etc. or '' for mainnet
|
private apiBasePath = ''; // network path is /testnet, etc. or '' for mainnet
|
||||||
|
|
||||||
private requestCache = new Map<string, { subject: BehaviorSubject<any>, expiry: number }>;
|
private requestCache = new Map<string, { subject: BehaviorSubject<any>, expiry: number }>;
|
||||||
|
@ -16,6 +17,10 @@ export class LightningApiService {
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
) {
|
) {
|
||||||
|
this.apiBaseUrl = ''; // use relative URL by default
|
||||||
|
if (!stateService.isBrowser) { // except when inside AU SSR process
|
||||||
|
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
|
||||||
|
}
|
||||||
this.apiBasePath = ''; // assume mainnet by default
|
this.apiBasePath = ''; // assume mainnet by default
|
||||||
this.stateService.networkChanged$.subscribe((network) => {
|
this.stateService.networkChanged$.subscribe((network) => {
|
||||||
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
|
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
|
||||||
|
@ -66,15 +71,15 @@ export class LightningApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode$(publicKey: string): Observable<any> {
|
getNode$(publicKey: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey);
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeGroup$(name: string): Observable<any[]> {
|
getNodeGroup$(name: string): Observable<any[]> {
|
||||||
return this.httpClient.get<any[]>(this.apiBasePath + '/api/v1/lightning/nodes/group/' + name);
|
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/group/' + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChannel$(shortId: string): Observable<any> {
|
getChannel$(shortId: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels/' + shortId);
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels/' + shortId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChannelsByNodeId$(publicKey: string, index: number = 0, status = 'open'): Observable<any> {
|
getChannelsByNodeId$(publicKey: string, index: number = 0, status = 'open'): Observable<any> {
|
||||||
|
@ -84,57 +89,57 @@ export class LightningApiService {
|
||||||
.set('status', status)
|
.set('status', status)
|
||||||
;
|
;
|
||||||
|
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels', { params, observe: 'response' });
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels', { params, observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestStatistics$(): Observable<any> {
|
getLatestStatistics$(): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/statistics/latest');
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/statistics/latest');
|
||||||
}
|
}
|
||||||
|
|
||||||
listNodeStats$(publicKey: string): Observable<any> {
|
listNodeStats$(publicKey: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics');
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics');
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeFeeHistogram$(publicKey: string): Observable<any> {
|
getNodeFeeHistogram$(publicKey: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/fees/histogram');
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/fees/histogram');
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodesRanking$(): Observable<INodesRanking> {
|
getNodesRanking$(): Observable<INodesRanking> {
|
||||||
return this.httpClient.get<INodesRanking>(this.apiBasePath + '/api/v1/lightning/nodes/rankings');
|
return this.httpClient.get<INodesRanking>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings');
|
||||||
}
|
}
|
||||||
|
|
||||||
listChannelStats$(publicKey: string): Observable<any> {
|
listChannelStats$(publicKey: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/channels/' + publicKey + '/statistics');
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/channels/' + publicKey + '/statistics');
|
||||||
}
|
}
|
||||||
|
|
||||||
listStatistics$(interval: string | undefined): Observable<any> {
|
listStatistics$(interval: string | undefined): Observable<any> {
|
||||||
return this.httpClient.get<any>(
|
return this.httpClient.get<any>(
|
||||||
this.apiBasePath + '/api/v1/lightning/statistics' +
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/statistics' +
|
||||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTopNodesByCapacity$(): Observable<ITopNodesPerCapacity[]> {
|
getTopNodesByCapacity$(): Observable<ITopNodesPerCapacity[]> {
|
||||||
return this.httpClient.get<ITopNodesPerCapacity[]>(
|
return this.httpClient.get<ITopNodesPerCapacity[]>(
|
||||||
this.apiBasePath + '/api/v1/lightning/nodes/rankings/liquidity'
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/liquidity'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTopNodesByChannels$(): Observable<ITopNodesPerChannels[]> {
|
getTopNodesByChannels$(): Observable<ITopNodesPerChannels[]> {
|
||||||
return this.httpClient.get<ITopNodesPerChannels[]>(
|
return this.httpClient.get<ITopNodesPerChannels[]>(
|
||||||
this.apiBasePath + '/api/v1/lightning/nodes/rankings/connectivity'
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/connectivity'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPenaltyClosedChannels$(): Observable<IChannel[]> {
|
getPenaltyClosedChannels$(): Observable<IChannel[]> {
|
||||||
return this.httpClient.get<IChannel[]>(
|
return this.httpClient.get<IChannel[]>(
|
||||||
this.apiBasePath + '/api/v1/lightning/penalties'
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/penalties'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOldestNodes$(): Observable<IOldestNodes[]> {
|
getOldestNodes$(): Observable<IOldestNodes[]> {
|
||||||
return this.httpClient.get<IOldestNodes[]>(
|
return this.httpClient.get<IOldestNodes[]>(
|
||||||
this.apiBasePath + '/api/v1/lightning/nodes/rankings/age'
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/age'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, merge } from 'rxjs';
|
||||||
import { share } from 'rxjs/operators';
|
import { share } from 'rxjs/operators';
|
||||||
import { INodesRanking, INodesStatistics } from '../../interfaces/node-api.interface';
|
import { INodesRanking, INodesStatistics } from '../../interfaces/node-api.interface';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
@ -24,6 +24,7 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit {
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private ogService: OpenGraphService,
|
private ogService: OpenGraphService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -35,6 +36,12 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
|
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
|
||||||
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
|
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
|
||||||
|
|
||||||
|
if (!this.stateService.isBrowser) {
|
||||||
|
merge(this.nodesRanking$, this.statistics$).subscribe(() => {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="full-container">
|
<div class="full-container">
|
||||||
<h2 i18n="lightning.node-fee-distribution">Fee distribution</h2>
|
<h2 i18n="lightning.node-fee-distribution">Fee distribution</h2>
|
||||||
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
.full-container {
|
.full-container {
|
||||||
|
position: relative;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
min-height: 450px;
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import { download } from '../../shared/graphs.utils';
|
||||||
import { LightningApiService } from '../lightning-api.service';
|
import { LightningApiService } from '../lightning-api.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-node-fee-chart',
|
selector: 'app-node-fee-chart',
|
||||||
|
@ -33,6 +34,7 @@ export class NodeFeeChartComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private lightningApiService: LightningApiService,
|
private lightningApiService: LightningApiService,
|
||||||
|
public stateService: StateService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="full-container">
|
<div class="full-container">
|
||||||
|
|
||||||
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { StorageService } from '../../services/storage.service';
|
||||||
import { download } from '../../shared/graphs.utils';
|
import { download } from '../../shared/graphs.utils';
|
||||||
import { LightningApiService } from '../lightning-api.service';
|
import { LightningApiService } from '../lightning-api.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-node-statistics-chart',
|
selector: 'app-node-statistics-chart',
|
||||||
|
@ -48,6 +49,7 @@ export class NodeStatisticsChartComponent implements OnInit {
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private lightningApiService: LightningApiService,
|
private lightningApiService: LightningApiService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
|
public stateService: StateService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<div class="map-wrapper" [class]="style" *ngIf="style !== 'graph'">
|
<div class="map-wrapper" [class]="style" *ngIf="style !== 'graph'">
|
||||||
<ng-container *ngIf="channelsObservable | async">
|
<ng-container *ngIf="channelsObservable | async">
|
||||||
<div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')">
|
<div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')">
|
||||||
<div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart" [class]="style" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div>
|
<div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loading-spinner" [class]="style" *ngIf="isLoading && !disableSpinner">
|
|
||||||
|
<div class="text-center loading-spinner" [class]="style" *ngIf="(!stateService.isBrowser || isLoading) && !disableSpinner">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -21,8 +22,10 @@
|
||||||
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="channelsObservable | async" class="chart-graph" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<ng-container *ngIf="channelsObservable | async">
|
||||||
|
<div class="chart-graph" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
min-height: 100%;
|
|
||||||
}
|
}
|
||||||
.full-container.widget {
|
.full-container.widget {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class NodesChannelsMap implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
<div class="node-channels-container">
|
||||||
<div *ngIf="channelsObservable$ | async" style="min-height: 455px">
|
<div *ngIf="channelsObservable$ | async" style="min-height: 455px">
|
||||||
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
|
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
|
||||||
<div echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
|
<div *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isLoading" class="text-center loading-spinner">
|
<div *ngIf="!stateService.isBrowser || isLoading" class="text-center loading-spinner">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
|
.node-channels-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
min-height: 455px;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ export class NodeChannels implements OnChanges {
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
|
|
|
@ -7,8 +7,13 @@
|
||||||
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="observable$ | async" class="chart" [class]="widget ? 'widget' : ''" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<ng-container *ngIf="observable$ | async">
|
||||||
|
<div class="chart" [class]="widget ? 'widget' : ''" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<div class="text-center loading-spinner" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,3 +63,13 @@
|
||||||
.chart.widget {
|
.chart.widget {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(50% - 15px);
|
||||||
|
z-index: 100;
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
top: 550px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ export class NodesMap implements OnInit, OnChanges {
|
||||||
inputNodes$: BehaviorSubject<any>;
|
inputNodes$: BehaviorSubject<any>;
|
||||||
nodes$: Observable<any>;
|
nodes$: Observable<any>;
|
||||||
observable$: Observable<any>;
|
observable$: Observable<any>;
|
||||||
|
isLoading: boolean = true;
|
||||||
|
|
||||||
chartInstance = undefined;
|
chartInstance = undefined;
|
||||||
chartOptions: EChartsOption = {};
|
chartOptions: EChartsOption = {};
|
||||||
|
@ -37,7 +38,7 @@ export class NodesMap implements OnInit, OnChanges {
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
@ -226,6 +227,7 @@ export class NodesMap implements OnInit, OnChanges {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChartInit(ec) {
|
onChartInit(ec) {
|
||||||
|
@ -235,6 +237,10 @@ export class NodesMap implements OnInit, OnChanges {
|
||||||
|
|
||||||
this.chartInstance = ec;
|
this.chartInstance = ec;
|
||||||
|
|
||||||
|
this.chartInstance.on('finished', () => {
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
this.chartInstance.on('click', (e) => {
|
this.chartInstance.on('click', (e) => {
|
||||||
if (e.data) {
|
if (e.data) {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
|
|
|
@ -35,8 +35,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class]="!widget ? 'chart' : 'chart-widget'" [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { SeoService } from '../../services/seo.service';
|
||||||
import { LightningApiService } from '../lightning-api.service';
|
import { LightningApiService } from '../lightning-api.service';
|
||||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||||
import { isMobile } from '../../shared/common.utils';
|
import { isMobile } from '../../shared/common.utils';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-nodes-networks-chart',
|
selector: 'app-nodes-networks-chart',
|
||||||
|
@ -58,6 +59,7 @@ export class NodesNetworksChartComponent implements OnInit, OnChanges {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
public stateService: StateService,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
|
|
||||||
<div class="container pb-lg-0">
|
<div class="container pb-lg-0">
|
||||||
<div class="pb-lg-5">
|
<div class="pb-lg-5">
|
||||||
<div class="chart w-100" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div class="chart w-100" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class NodesPerCountryChartComponent implements OnInit {
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
|
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
|
@ -27,6 +27,7 @@ export class NodesPerCountry implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < this.pageSize; ++i) {
|
for (let i = 0; i < this.pageSize; ++i) {
|
||||||
|
@ -94,7 +95,10 @@ export class NodesPerCountry implements OnInit {
|
||||||
ispCount: Object.keys(isps).length
|
ispCount: Object.keys(isps).length
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
tap(() => this.isLoading = false),
|
tap(() => {
|
||||||
|
this.isLoading = false
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}),
|
||||||
share()
|
share()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -39,14 +39,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!indexingInProgress else indexing" [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
|
<div *ngIf="!indexingInProgress else indexing" [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
|
||||||
<div [class]="widget ? 'chart-widget' : 'chart'" [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class]="widget ? 'chart-widget' : 'chart'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
|
||||||
<div class="spinner-border text-light"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-md-end toggle" *ngIf="!widget">
|
<div class="d-flex justify-content-md-end toggle" *ngIf="!widget">
|
||||||
<app-toggle [textLeft]="'Sort by nodes'" [textRight]="'capacity'" [checked]="true" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
|
<app-toggle [textLeft]="'Sort by nodes'" [textRight]="'capacity'" [checked]="true" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,6 +70,9 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #loadingReward>
|
<ng-template #loadingReward>
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,9 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class]="!widget ? 'chart' : 'chart-widget'" [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)"></div>
|
(chartInit)="onChartInit($event)"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { download } from '../../shared/graphs.utils';
|
||||||
import { LightningApiService } from '../lightning-api.service';
|
import { LightningApiService } from '../lightning-api.service';
|
||||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||||
import { isMobile } from '../../shared/common.utils';
|
import { isMobile } from '../../shared/common.utils';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-lightning-statistics-chart',
|
selector: 'app-lightning-statistics-chart',
|
||||||
|
@ -55,6 +56,7 @@ export class LightningStatisticsChartComponent implements OnInit, OnChanges {
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
|
public stateService: StateService,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||||
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
|
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||||
|
@ -17,14 +17,18 @@ export class HttpCacheInterceptor implements HttpInterceptor {
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
if (this.isBrowser && request.method === 'GET') {
|
if (this.isBrowser && request.method === 'GET') {
|
||||||
|
|
||||||
const cachedResponse = this.transferState.get<any>(makeStateKey(request.url), null);
|
const { response, headers } = this.transferState.get<any>(makeStateKey(request.url), null) || {};
|
||||||
if (cachedResponse) {
|
if (response) {
|
||||||
|
const httpHeaders = new HttpHeaders();
|
||||||
|
for (const [k,v] of Object.entries(headers)) {
|
||||||
|
httpHeaders.set(k,v as string[]);
|
||||||
|
}
|
||||||
const modifiedResponse = new HttpResponse<any>({
|
const modifiedResponse = new HttpResponse<any>({
|
||||||
headers: cachedResponse.headers,
|
headers: httpHeaders,
|
||||||
body: cachedResponse.body,
|
body: response.body,
|
||||||
status: cachedResponse.status,
|
status: response.status,
|
||||||
statusText: cachedResponse.statusText,
|
statusText: response.statusText,
|
||||||
url: cachedResponse.url
|
url: response.url
|
||||||
});
|
});
|
||||||
this.transferState.remove(makeStateKey(request.url));
|
this.transferState.remove(makeStateKey(request.url));
|
||||||
return of(modifiedResponse);
|
return of(modifiedResponse);
|
||||||
|
@ -35,7 +39,11 @@ export class HttpCacheInterceptor implements HttpInterceptor {
|
||||||
.pipe(tap((event: HttpEvent<any>) => {
|
.pipe(tap((event: HttpEvent<any>) => {
|
||||||
if (!this.isBrowser && event instanceof HttpResponse) {
|
if (!this.isBrowser && event instanceof HttpResponse) {
|
||||||
let keyId = request.url.split('/').slice(3).join('/');
|
let keyId = request.url.split('/').slice(3).join('/');
|
||||||
this.transferState.set<any>(makeStateKey('/' + keyId), event);
|
const headers = {};
|
||||||
|
for (const k of event.headers.keys()) {
|
||||||
|
headers[k] = event.headers.getAll(k);
|
||||||
|
}
|
||||||
|
this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers });
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { Subscription } from 'rxjs';
|
import { firstValueFrom, Subscription } from 'rxjs';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
import { TransferState, makeStateKey } from '@angular/core';
|
||||||
import { CacheService } from './cache.service';
|
import { CacheService } from './cache.service';
|
||||||
import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils';
|
import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils';
|
||||||
|
|
||||||
|
@ -57,8 +57,12 @@ export class WebsocketService {
|
||||||
this.network = this.stateService.network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND ? '' : this.stateService.network;
|
this.network = this.stateService.network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND ? '' : this.stateService.network;
|
||||||
this.websocketSubject = webSocket<WebsocketResponse>(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : ''));
|
this.websocketSubject = webSocket<WebsocketResponse>(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : ''));
|
||||||
|
|
||||||
const theInitData = this.transferState.get<any>(initData, null);
|
const { response: theInitData } = this.transferState.get<any>(initData, null) || {};
|
||||||
if (theInitData) {
|
if (theInitData) {
|
||||||
|
if (theInitData.body.blocks) {
|
||||||
|
theInitData.body.blocks = theInitData.body.blocks.reverse();
|
||||||
|
}
|
||||||
|
this.stateService.isLoadingWebSocket$.next(false);
|
||||||
this.handleResponse(theInitData.body);
|
this.handleResponse(theInitData.body);
|
||||||
this.startSubscription(false, true);
|
this.startSubscription(false, true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,6 +227,7 @@ export class WebsocketService {
|
||||||
}
|
}
|
||||||
|
|
||||||
startTrackRbfSummary() {
|
startTrackRbfSummary() {
|
||||||
|
this.initRbfSummary();
|
||||||
this.websocketSubject.next({ 'track-rbf-summary': true });
|
this.websocketSubject.next({ 'track-rbf-summary': true });
|
||||||
this.isTrackingRbfSummary = true;
|
this.isTrackingRbfSummary = true;
|
||||||
}
|
}
|
||||||
|
@ -445,4 +450,30 @@ export class WebsocketService {
|
||||||
this.websocketSubject.next({'refresh-blocks': true});
|
this.websocketSubject.next({'refresh-blocks': true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initRbfSummary(): Promise<void> {
|
||||||
|
if (!this.stateService.isBrowser) {
|
||||||
|
const rbfList = await firstValueFrom(this.apiService.getRbfList$(false));
|
||||||
|
if (rbfList) {
|
||||||
|
const rbfSummary = rbfList.slice(0, 6).map(rbfTree => {
|
||||||
|
let oldFee = 0;
|
||||||
|
let oldVsize = 0;
|
||||||
|
for (const replaced of rbfTree.replaces) {
|
||||||
|
oldFee += replaced.tx.fee;
|
||||||
|
oldVsize += replaced.tx.vsize;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
txid: rbfTree.tx.txid,
|
||||||
|
mined: !!rbfTree.tx.mined,
|
||||||
|
fullRbf: !!rbfTree.tx.fullRbf,
|
||||||
|
oldFee,
|
||||||
|
oldVsize,
|
||||||
|
newFee: rbfTree.tx.fee,
|
||||||
|
newVsize: rbfTree.tx.vsize,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.stateService.rbfLatestSummary$.next(rbfSummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
frontend/src/app/services/zone-shim.service.ts
Normal file
14
frontend/src/app/services/zone-shim.service.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ZoneService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
wrapObservable<T>(obs: Observable<T>): Observable<T> {
|
||||||
|
return obs;
|
||||||
|
}
|
||||||
|
}
|
60
frontend/src/app/services/zone.service.ts
Normal file
60
frontend/src/app/services/zone.service.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { ApplicationRef, Injectable, NgZone } from '@angular/core';
|
||||||
|
import { Observable, Subscriber } from 'rxjs';
|
||||||
|
|
||||||
|
// global Zone object provided by zone.js
|
||||||
|
declare const Zone: any;
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ZoneService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private appRef: ApplicationRef,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
wrapObservable<T>(obs: Observable<T>): Observable<T> {
|
||||||
|
return new Observable((subscriber: Subscriber<T>) => {
|
||||||
|
let task: any;
|
||||||
|
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
task = Zone.current.scheduleMacroTask('wrapObservable', () => {}, {}, () => {}, () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
const subscription = obs.subscribe(
|
||||||
|
value => {
|
||||||
|
subscriber.next(value);
|
||||||
|
if (task) {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this.appRef.tick();
|
||||||
|
});
|
||||||
|
task.invoke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
subscriber.error(err);
|
||||||
|
if (task) {
|
||||||
|
this.appRef.tick();
|
||||||
|
task.invoke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
subscriber.complete();
|
||||||
|
if (task) {
|
||||||
|
this.appRef.tick();
|
||||||
|
task.invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
if (task) {
|
||||||
|
this.appRef.tick();
|
||||||
|
task.invoke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/app/shared/directives/browser-only.directive.ts
Normal file
19
frontend/src/app/shared/directives/browser-only.directive.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Directive, TemplateRef, ViewContainerRef, Inject, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[browserOnly]'
|
||||||
|
})
|
||||||
|
export class BrowserOnlyDirective {
|
||||||
|
constructor(
|
||||||
|
private templateRef: TemplateRef<any>,
|
||||||
|
private viewContainer: ViewContainerRef,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object
|
||||||
|
) {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||||
|
} else {
|
||||||
|
this.viewContainer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/app/shared/directives/server-only.directive.ts
Normal file
19
frontend/src/app/shared/directives/server-only.directive.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Directive, TemplateRef, ViewContainerRef, Inject, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { isPlatformServer } from '@angular/common';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[serverOnly]'
|
||||||
|
})
|
||||||
|
export class ServerOnlyDirective {
|
||||||
|
constructor(
|
||||||
|
private templateRef: TemplateRef<any>,
|
||||||
|
private viewContainer: ViewContainerRef,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object
|
||||||
|
) {
|
||||||
|
if (isPlatformServer(this.platformId)) {
|
||||||
|
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||||
|
} else {
|
||||||
|
this.viewContainer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -323,7 +323,7 @@ export function hasTouchScreen(): boolean {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
hasTouchScreen = navigator.msMaxTouchPoints > 0;
|
hasTouchScreen = navigator.msMaxTouchPoints > 0;
|
||||||
} else {
|
} else {
|
||||||
const mQ = matchMedia?.('(pointer:coarse)');
|
const mQ = window.matchMedia?.('(pointer:coarse)');
|
||||||
if (mQ?.media === '(pointer:coarse)') {
|
if (mQ?.media === '(pointer:coarse)') {
|
||||||
hasTouchScreen = !!mQ.matches;
|
hasTouchScreen = !!mQ.matches;
|
||||||
} else if ('orientation' in window) {
|
} else if ('orientation' in window) {
|
||||||
|
|
|
@ -33,6 +33,8 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { LanguageSelectorComponent } from '../components/language-selector/language-selector.component';
|
import { LanguageSelectorComponent } from '../components/language-selector/language-selector.component';
|
||||||
import { FiatSelectorComponent } from '../components/fiat-selector/fiat-selector.component';
|
import { FiatSelectorComponent } from '../components/fiat-selector/fiat-selector.component';
|
||||||
import { RateUnitSelectorComponent } from '../components/rate-unit-selector/rate-unit-selector.component';
|
import { RateUnitSelectorComponent } from '../components/rate-unit-selector/rate-unit-selector.component';
|
||||||
|
import { BrowserOnlyDirective } from './directives/browser-only.directive';
|
||||||
|
import { ServerOnlyDirective } from './directives/server-only.directive';
|
||||||
import { ColoredPriceDirective } from './directives/colored-price.directive';
|
import { ColoredPriceDirective } from './directives/colored-price.directive';
|
||||||
import { NoSanitizePipe } from './pipes/no-sanitize.pipe';
|
import { NoSanitizePipe } from './pipes/no-sanitize.pipe';
|
||||||
import { MempoolBlocksComponent } from '../components/mempool-blocks/mempool-blocks.component';
|
import { MempoolBlocksComponent } from '../components/mempool-blocks/mempool-blocks.component';
|
||||||
|
@ -132,6 +134,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
||||||
FeeRoundingPipe,
|
FeeRoundingPipe,
|
||||||
FiatCurrencyPipe,
|
FiatCurrencyPipe,
|
||||||
ColoredPriceDirective,
|
ColoredPriceDirective,
|
||||||
|
BrowserOnlyDirective,
|
||||||
|
ServerOnlyDirective,
|
||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
BlockViewComponent,
|
BlockViewComponent,
|
||||||
EightBlocksComponent,
|
EightBlocksComponent,
|
||||||
|
@ -264,6 +268,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
||||||
Decimal2HexPipe,
|
Decimal2HexPipe,
|
||||||
FeeRoundingPipe,
|
FeeRoundingPipe,
|
||||||
ColoredPriceDirective,
|
ColoredPriceDirective,
|
||||||
|
BrowserOnlyDirective,
|
||||||
|
ServerOnlyDirective,
|
||||||
NoSanitizePipe,
|
NoSanitizePipe,
|
||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
MempoolBlocksComponent,
|
MempoolBlocksComponent,
|
||||||
|
|
11
frontend/src/main.server.ts
Normal file
11
frontend/src/main.server.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
import '@angular/localize/init';
|
||||||
|
import { enableProdMode } from '@angular/core';
|
||||||
|
import { environment } from './environments/environment';
|
||||||
|
|
||||||
|
if (environment.production) {
|
||||||
|
enableProdMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AppServerModule } from './app/app.server.module';
|
||||||
|
export { renderModule } from '@angular/platform-server';
|
18
frontend/tsconfig.server.json
Normal file
18
frontend/tsconfig.server.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/server",
|
||||||
|
"target": "ES2022",
|
||||||
|
"sourceMap": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.server.ts",
|
||||||
|
"server.ts"
|
||||||
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"entryModule": "./src/app/app.server.module#AppServerModule"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue