Merge branch 'master' into natsoni/statistics-replication
This commit is contained in:
commit
b0630de3cc
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -257,7 +257,7 @@ jobs:
|
|||||||
spec: |
|
spec: |
|
||||||
cypress/e2e/mainnet/*.spec.ts
|
cypress/e2e/mainnet/*.spec.ts
|
||||||
cypress/e2e/signet/*.spec.ts
|
cypress/e2e/signet/*.spec.ts
|
||||||
cypress/e2e/testnet/*.spec.ts
|
cypress/e2e/testnet4/*.spec.ts
|
||||||
- module: "liquid"
|
- module: "liquid"
|
||||||
spec: |
|
spec: |
|
||||||
cypress/e2e/liquid/liquid.spec.ts
|
cypress/e2e/liquid/liquid.spec.ts
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@typescript-eslint/no-this-alias": 1,
|
"@typescript-eslint/no-this-alias": 1,
|
||||||
"@typescript-eslint/no-var-requires": 1,
|
"@typescript-eslint/no-var-requires": 1,
|
||||||
"@typescript-eslint/explicit-function-return-type": 1,
|
"@typescript-eslint/explicit-function-return-type": 1,
|
||||||
|
"@typescript-eslint/no-unused-vars": 1,
|
||||||
"no-console": 1,
|
"no-console": 1,
|
||||||
"no-constant-condition": 1,
|
"no-constant-condition": 1,
|
||||||
"no-dupe-else-if": 1,
|
"no-dupe-else-if": 1,
|
||||||
@ -32,6 +33,7 @@
|
|||||||
"prefer-rest-params": 1,
|
"prefer-rest-params": 1,
|
||||||
"quotes": [1, "single", { "allowTemplateLiterals": true }],
|
"quotes": [1, "single", { "allowTemplateLiterals": true }],
|
||||||
"semi": 1,
|
"semi": 1,
|
||||||
|
"curly": [1, "all"],
|
||||||
"eqeqeq": 1
|
"eqeqeq": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -839,9 +839,12 @@ class Blocks {
|
|||||||
} else {
|
} else {
|
||||||
this.currentBlockHeight++;
|
this.currentBlockHeight++;
|
||||||
logger.debug(`New block found (#${this.currentBlockHeight})!`);
|
logger.debug(`New block found (#${this.currentBlockHeight})!`);
|
||||||
|
// skip updating the orphan block cache if we've fallen behind the chain tip
|
||||||
|
if (this.currentBlockHeight >= blockHeightTip - 2) {
|
||||||
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
|
||||||
await chainTips.updateOrphanedBlocks();
|
await chainTips.updateOrphanedBlocks();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.updateTimerProgress(timer, `getting block data for ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `getting block data for ${this.currentBlockHeight}`);
|
||||||
const blockHash = await bitcoinCoreApi.$getBlockHash(this.currentBlockHeight);
|
const blockHash = await bitcoinCoreApi.$getBlockHash(this.currentBlockHeight);
|
||||||
|
@ -12,32 +12,68 @@ export interface OrphanedBlock {
|
|||||||
height: number;
|
height: number;
|
||||||
hash: string;
|
hash: string;
|
||||||
status: 'valid-fork' | 'valid-headers' | 'headers-only';
|
status: 'valid-fork' | 'valid-headers' | 'headers-only';
|
||||||
|
prevhash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChainTips {
|
class ChainTips {
|
||||||
private chainTips: ChainTip[] = [];
|
private chainTips: ChainTip[] = [];
|
||||||
private orphanedBlocks: OrphanedBlock[] = [];
|
private orphanedBlocks: { [hash: string]: OrphanedBlock } = {};
|
||||||
|
private blockCache: { [hash: string]: OrphanedBlock } = {};
|
||||||
|
private orphansByHeight: { [height: number]: OrphanedBlock[] } = {};
|
||||||
|
|
||||||
public async updateOrphanedBlocks(): Promise<void> {
|
public async updateOrphanedBlocks(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.chainTips = await bitcoinClient.getChainTips();
|
this.chainTips = await bitcoinClient.getChainTips();
|
||||||
this.orphanedBlocks = [];
|
|
||||||
|
const start = Date.now();
|
||||||
|
const breakAt = start + 10000;
|
||||||
|
let newOrphans = 0;
|
||||||
|
this.orphanedBlocks = {};
|
||||||
|
|
||||||
for (const chain of this.chainTips) {
|
for (const chain of this.chainTips) {
|
||||||
if (chain.status === 'valid-fork' || chain.status === 'valid-headers') {
|
if (chain.status === 'valid-fork' || chain.status === 'valid-headers') {
|
||||||
let block = await bitcoinClient.getBlock(chain.hash);
|
const orphans: OrphanedBlock[] = [];
|
||||||
while (block && block.confirmations === -1) {
|
let hash = chain.hash;
|
||||||
this.orphanedBlocks.push({
|
do {
|
||||||
|
let orphan = this.blockCache[hash];
|
||||||
|
if (!orphan) {
|
||||||
|
const block = await bitcoinClient.getBlock(hash);
|
||||||
|
if (block && block.confirmations === -1) {
|
||||||
|
newOrphans++;
|
||||||
|
orphan = {
|
||||||
height: block.height,
|
height: block.height,
|
||||||
hash: block.hash,
|
hash: block.hash,
|
||||||
status: chain.status
|
status: chain.status,
|
||||||
});
|
prevhash: block.previousblockhash,
|
||||||
block = await bitcoinClient.getBlock(block.previousblockhash);
|
};
|
||||||
|
this.blockCache[hash] = orphan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (orphan) {
|
||||||
|
orphans.push(orphan);
|
||||||
|
}
|
||||||
|
hash = orphan?.prevhash;
|
||||||
|
} while (hash && (Date.now() < breakAt));
|
||||||
|
for (const orphan of orphans) {
|
||||||
|
this.orphanedBlocks[orphan.hash] = orphan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Date.now() >= breakAt) {
|
||||||
|
logger.debug(`Breaking orphaned blocks updater after 10s, will continue next block`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`Updated orphaned blocks cache. Found ${this.orphanedBlocks.length} orphaned blocks`);
|
this.orphansByHeight = {};
|
||||||
|
const allOrphans = Object.values(this.orphanedBlocks);
|
||||||
|
for (const orphan of allOrphans) {
|
||||||
|
if (!this.orphansByHeight[orphan.height]) {
|
||||||
|
this.orphansByHeight[orphan.height] = [];
|
||||||
|
}
|
||||||
|
this.orphansByHeight[orphan.height].push(orphan);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Updated orphaned blocks cache. Fetched ${newOrphans} new orphaned blocks. Total ${allOrphans.length}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot get fetch orphaned blocks. Reason: ${e instanceof Error ? e.message : e}`);
|
logger.err(`Cannot get fetch orphaned blocks. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
}
|
}
|
||||||
@ -48,13 +84,7 @@ class ChainTips {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const orphans: OrphanedBlock[] = [];
|
return this.orphansByHeight[height] || [];
|
||||||
for (const block of this.orphanedBlocks) {
|
|
||||||
if (block.height === height) {
|
|
||||||
orphans.push(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orphans;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', this.$getDifficultyAdjustments)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', this.$getDifficultyAdjustments)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', this.$getRewardStats)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', this.$getRewardStats)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', this.$getHistoricalBlockFees)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', this.$getHistoricalBlockFees)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees', this.$getBlockFeesTimespan)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', this.$getHistoricalBlockRewards)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', this.$getHistoricalBlockRewards)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
||||||
@ -217,6 +218,26 @@ class MiningRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getBlockFeesTimespan(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
if (!parseInt(req.query.from as string, 10) || !parseInt(req.query.to as string, 10)) {
|
||||||
|
throw new Error('Invalid timestamp range');
|
||||||
|
}
|
||||||
|
if (parseInt(req.query.from as string, 10) > parseInt(req.query.to as string, 10)) {
|
||||||
|
throw new Error('from must be less than to');
|
||||||
|
}
|
||||||
|
const blockFees = await mining.$getBlockFeesTimespan(parseInt(req.query.from as string, 10), parseInt(req.query.to as string, 10));
|
||||||
|
const blockCount = await BlocksRepository.$blockCount(null, null);
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.header('X-total-count', blockCount.toString());
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
|
res.json(blockFees);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async $getHistoricalBlockRewards(req: Request, res: Response) {
|
private async $getHistoricalBlockRewards(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval);
|
const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval);
|
||||||
|
@ -45,11 +45,22 @@ class Mining {
|
|||||||
*/
|
*/
|
||||||
public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
|
public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
|
||||||
return await BlocksRepository.$getHistoricalBlockFees(
|
return await BlocksRepository.$getHistoricalBlockFees(
|
||||||
this.getTimeRange(interval, 5),
|
this.getTimeRange(interval),
|
||||||
Common.getSqlInterval(interval)
|
Common.getSqlInterval(interval)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timespan block total fees
|
||||||
|
*/
|
||||||
|
public async $getBlockFeesTimespan(from: number, to: number): Promise<number> {
|
||||||
|
return await BlocksRepository.$getHistoricalBlockFees(
|
||||||
|
this.getTimeRangeFromTimespan(from, to),
|
||||||
|
null,
|
||||||
|
{from, to}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get historical block rewards
|
* Get historical block rewards
|
||||||
*/
|
*/
|
||||||
@ -646,6 +657,24 @@ class Mining {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTimeRangeFromTimespan(from: number, to: number, scale = 1): number {
|
||||||
|
const timespan = to - from;
|
||||||
|
switch (true) {
|
||||||
|
case timespan > 3600 * 24 * 365 * 4: return 86400 * scale; // 24h
|
||||||
|
case timespan > 3600 * 24 * 365 * 3: return 43200 * scale; // 12h
|
||||||
|
case timespan > 3600 * 24 * 365 * 2: return 43200 * scale; // 12h
|
||||||
|
case timespan > 3600 * 24 * 365: return 28800 * scale; // 8h
|
||||||
|
case timespan > 3600 * 24 * 30 * 6: return 28800 * scale; // 8h
|
||||||
|
case timespan > 3600 * 24 * 30 * 3: return 10800 * scale; // 3h
|
||||||
|
case timespan > 3600 * 24 * 30: return 7200 * scale; // 2h
|
||||||
|
case timespan > 3600 * 24 * 7: return 1800 * scale; // 30min
|
||||||
|
case timespan > 3600 * 24 * 3: return 300 * scale; // 5min
|
||||||
|
case timespan > 3600 * 24: return 1 * scale;
|
||||||
|
default: return 1 * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Finds the oldest block in a consecutive chain back from the tip
|
// Finds the oldest block in a consecutive chain back from the tip
|
||||||
// assumes `blocks` is sorted in ascending height order
|
// assumes `blocks` is sorted in ascending height order
|
||||||
private getOldestConsecutiveBlock(blocks: DifficultyBlock[]): DifficultyBlock {
|
private getOldestConsecutiveBlock(blocks: DifficultyBlock[]): DifficultyBlock {
|
||||||
|
@ -663,7 +663,7 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Get the historical averaged block fees
|
* Get the historical averaged block fees
|
||||||
*/
|
*/
|
||||||
public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
|
public async $getHistoricalBlockFees(div: number, interval: string | null, timespan?: {from: number, to: number}): Promise<any> {
|
||||||
try {
|
try {
|
||||||
let query = `SELECT
|
let query = `SELECT
|
||||||
CAST(AVG(blocks.height) as INT) as avgHeight,
|
CAST(AVG(blocks.height) as INT) as avgHeight,
|
||||||
@ -677,6 +677,8 @@ class BlocksRepository {
|
|||||||
|
|
||||||
if (interval !== null) {
|
if (interval !== null) {
|
||||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||||
|
} else if (timespan) {
|
||||||
|
query += ` WHERE blockTimestamp BETWEEN FROM_UNIXTIME(${timespan.from}) AND FROM_UNIXTIME(${timespan.to})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`;
|
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`;
|
||||||
|
3
contributors/bitcoinmechanic.txt
Normal file
3
contributors/bitcoinmechanic.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022.
|
||||||
|
|
||||||
|
Signed: bitcoinmechanic
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:20.12.0-buster-slim AS builder
|
FROM node:20.13.1-buster-slim AS builder
|
||||||
|
|
||||||
ARG commitHash
|
ARG commitHash
|
||||||
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
||||||
@ -24,7 +24,7 @@ RUN npm install --omit=dev --omit=optional
|
|||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
RUN npm run package
|
RUN npm run package
|
||||||
|
|
||||||
FROM node:20.12.0-buster-slim
|
FROM node:20.13.1-buster-slim
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:20.12.0-buster-slim AS builder
|
FROM node:20.13.1-buster-slim AS builder
|
||||||
|
|
||||||
ARG commitHash
|
ARG commitHash
|
||||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"prefer-rest-params": 1,
|
"prefer-rest-params": 1,
|
||||||
"quotes": [1, "single", { "allowTemplateLiterals": true }],
|
"quotes": [1, "single", { "allowTemplateLiterals": true }],
|
||||||
"semi": 1,
|
"semi": 1,
|
||||||
|
"curly": [1, "all"],
|
||||||
"eqeqeq": 1
|
"eqeqeq": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,11 @@
|
|||||||
"bundleName": "wiz",
|
"bundleName": "wiz",
|
||||||
"inject": false
|
"inject": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"input": "src/theme-bukele.scss",
|
||||||
|
"bundleName": "bukele",
|
||||||
|
"inject": false
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
],
|
],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
|
@ -1,30 +1,37 @@
|
|||||||
{
|
{
|
||||||
"theme": "contrast",
|
"theme": "bukele",
|
||||||
"enterprise": "onbtc",
|
"enterprise": "onbtc",
|
||||||
"branding": {
|
"branding": {
|
||||||
"name": "onbtc",
|
"name": "onbtc",
|
||||||
"title": "Oficina Nacional del Bitcoin",
|
"title": "Bitcoin Office",
|
||||||
"site_id": 19,
|
"site_id": 19,
|
||||||
"header_img": "/resources/onbtc.svg",
|
"header_img": "/resources/onbtclogo.svg",
|
||||||
"img": "/resources/elsalvador.svg",
|
"footer_img": "/resources/onbtclogo.svg",
|
||||||
"rounded_corner": true
|
"rounded_corner": true
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"widgets": [
|
"widgets": [
|
||||||
{
|
{
|
||||||
"component": "fees"
|
"component": "fees",
|
||||||
|
"mobileOrder": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "balance",
|
"component": "balance",
|
||||||
|
"mobileOrder": 1,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "goggles"
|
"component": "twitter",
|
||||||
|
"mobileOrder": 5,
|
||||||
|
"props": {
|
||||||
|
"handle": "nayibbukele"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "address",
|
"component": "address",
|
||||||
|
"mobileOrder": 2,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
|
||||||
"period": "1m"
|
"period": "1m"
|
||||||
@ -35,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "addressTransactions",
|
"component": "addressTransactions",
|
||||||
|
"mobileOrder": 3,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,8 @@ describe('Mainnet', () => {
|
|||||||
it('check op_return coinbase tooltip', () => {
|
it('check op_return coinbase tooltip', () => {
|
||||||
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
|
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('div > a > .badge').first().trigger('onmouseover');
|
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('onmouseover');
|
||||||
cy.get('div > a > .badge').first().trigger('mouseenter');
|
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('mouseenter');
|
||||||
cy.get('.tooltip-inner').should('be.visible');
|
cy.get('.tooltip-inner').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ describe('Mainnet', () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
|
|
||||||
cy.changeNetwork('testnet');
|
cy.changeNetwork('testnet4');
|
||||||
cy.changeNetwork('signet');
|
cy.changeNetwork('signet');
|
||||||
cy.changeNetwork('mainnet');
|
cy.changeNetwork('mainnet');
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { emitMempoolInfo } from '../../support/websocket';
|
|||||||
|
|
||||||
const baseModule = Cypress.env('BASE_MODULE');
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
|
|
||||||
describe('Testnet', () => {
|
describe('Testnet4', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('/api/block-height/*').as('block-height');
|
cy.intercept('/api/block-height/*').as('block-height');
|
||||||
cy.intercept('/api/block/*').as('block');
|
cy.intercept('/api/block/*').as('block');
|
||||||
@ -13,7 +13,7 @@ describe('Testnet', () => {
|
|||||||
if (baseModule === 'mempool') {
|
if (baseModule === 'mempool') {
|
||||||
|
|
||||||
it('loads the dashboard', () => {
|
it('loads the dashboard', () => {
|
||||||
cy.visit('/testnet');
|
cy.visit('/testnet4');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ describe('Testnet', () => {
|
|||||||
|
|
||||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||||
cy.mockMempoolSocket();
|
cy.mockMempoolSocket();
|
||||||
cy.visit('/testnet');
|
cy.visit('/testnet4');
|
||||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||||
@ -45,7 +45,7 @@ describe('Testnet', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads the pools screen', () => {
|
it('loads the pools screen', () => {
|
||||||
cy.visit('/testnet');
|
cy.visit('/testnet4');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('#btn-pools').click().then(() => {
|
cy.get('#btn-pools').click().then(() => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
@ -53,7 +53,7 @@ describe('Testnet', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads the graphs screen', () => {
|
it('loads the graphs screen', () => {
|
||||||
cy.visit('/testnet');
|
cy.visit('/testnet4');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('#btn-graphs').click().then(() => {
|
cy.get('#btn-graphs').click().then(() => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
@ -63,7 +63,7 @@ describe('Testnet', () => {
|
|||||||
describe('tv mode', () => {
|
describe('tv mode', () => {
|
||||||
it('loads the tv screen - desktop', () => {
|
it('loads the tv screen - desktop', () => {
|
||||||
cy.viewport('macbook-16');
|
cy.viewport('macbook-16');
|
||||||
cy.visit('/testnet/graphs');
|
cy.visit('/testnet4/graphs');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('#btn-tv').click().then(() => {
|
cy.get('#btn-tv').click().then(() => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
@ -73,7 +73,7 @@ describe('Testnet', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads the tv screen - mobile', () => {
|
it('loads the tv screen - mobile', () => {
|
||||||
cy.visit('/testnet/graphs');
|
cy.visit('/testnet4/graphs');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('#btn-tv').click().then(() => {
|
cy.get('#btn-tv').click().then(() => {
|
||||||
cy.viewport('iphone-6');
|
cy.viewport('iphone-6');
|
||||||
@ -85,7 +85,7 @@ describe('Testnet', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('loads the api screen', () => {
|
it('loads the api screen', () => {
|
||||||
cy.visit('/testnet');
|
cy.visit('/testnet4');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('#btn-docs').click().then(() => {
|
cy.get('#btn-docs').click().then(() => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
@ -94,13 +94,13 @@ describe('Testnet', () => {
|
|||||||
|
|
||||||
describe('blocks', () => {
|
describe('blocks', () => {
|
||||||
it('shows empty blocks properly', () => {
|
it('shows empty blocks properly', () => {
|
||||||
cy.visit('/testnet/block/0');
|
cy.visit('/testnet4/block/0');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('expands and collapses the block details', () => {
|
it('expands and collapses the block details', () => {
|
||||||
cy.visit('/testnet/block/0');
|
cy.visit('/testnet4/block/0');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||||
cy.get('#details').should('be.visible');
|
cy.get('#details').should('be.visible');
|
||||||
@ -112,15 +112,15 @@ describe('Testnet', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows blocks with no pagination', () => {
|
it('shows blocks with no pagination', () => {
|
||||||
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
|
cy.visit('/testnet4/block/000000000066e8b6cc78a93f8989587f5819624bae2eb1c05f535cadded19f99');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('h2').invoke('text').should('equal', '11 transactions');
|
cy.get('h2').invoke('text').should('equal', '18 transactions');
|
||||||
cy.get('ul.pagination').first().children().should('have.length', 5);
|
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports pagination on the block screen', () => {
|
it('supports pagination on the block screen', () => {
|
||||||
// 48 txs
|
// 48 txs
|
||||||
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
|
cy.visit('/testnet4/block/000000000000006982d53f8273bdff21dafc380c292eabc669b5ab6d732311c3');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||||
cy.get('.active + li').first().click().then(() => {
|
cy.get('.active + li').first().click().then(() => {
|
@ -72,7 +72,7 @@ Cypress.Commands.add('mockMempoolSocket', () => {
|
|||||||
mockWebSocket();
|
mockWebSocket();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('changeNetwork', (network: "testnet" | "signet" | "liquid" | "mainnet") => {
|
Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet" | "liquid" | "mainnet") => {
|
||||||
cy.get('.dropdown-toggle').click().then(() => {
|
cy.get('.dropdown-toggle').click().then(() => {
|
||||||
cy.get(`a.${network}`).click().then(() => {
|
cy.get(`a.${network}`).click().then(() => {
|
||||||
cy.waitForPageIdle();
|
cy.waitForPageIdle();
|
||||||
|
2
frontend/cypress/support/index.d.ts
vendored
2
frontend/cypress/support/index.d.ts
vendored
@ -5,6 +5,6 @@ declare namespace Cypress {
|
|||||||
waitForSkeletonGone(): Chainable<any>
|
waitForSkeletonGone(): Chainable<any>
|
||||||
waitForPageIdle(): Chainable<any>
|
waitForPageIdle(): Chainable<any>
|
||||||
mockMempoolSocket(): Chainable<any>
|
mockMempoolSocket(): Chainable<any>
|
||||||
changeNetwork(network: "testnet"|"signet"|"liquid"|"mainnet"): Chainable<any>
|
changeNetwork(network: "testnet"|"testnet4"|"signet"|"liquid"|"mainnet"): Chainable<any>
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,6 +10,8 @@ let settings = [];
|
|||||||
let configContent = {};
|
let configContent = {};
|
||||||
let gitCommitHash = '';
|
let gitCommitHash = '';
|
||||||
let packetJsonVersion = '';
|
let packetJsonVersion = '';
|
||||||
|
let customConfig;
|
||||||
|
let customConfigContent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
||||||
@ -23,7 +25,18 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexFilePath = configContent.BASE_MODULE ? 'src/index.' + configContent.BASE_MODULE + '.html' : 'src/index.mempool.html';
|
if (configContent && configContent.CUSTOMIZATION) {
|
||||||
|
try {
|
||||||
|
customConfig = readConfig(configContent.CUSTOMIZATION);
|
||||||
|
customConfigContent = JSON.parse(customConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`failed to load customization config from ${configContent.CUSTOMIZATION}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseModuleName = configContent.BASE_MODULE || 'mempool';
|
||||||
|
const customBuildName = (customConfigContent && customConfigContent.enterprise) ? ('.' + customConfigContent.enterprise) : '';
|
||||||
|
const indexFilePath = 'src/index.' + baseModuleName + customBuildName + '.html';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.copyFileSync(indexFilePath, 'src/index.html');
|
fs.copyFileSync(indexFilePath, 'src/index.html');
|
||||||
@ -111,20 +124,14 @@ writeConfigTemplate(GENERATED_TEMPLATE_CONFIG_FILE_NAME, newConfigTemplate);
|
|||||||
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
|
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
|
||||||
|
|
||||||
let customConfigJs = '';
|
let customConfigJs = '';
|
||||||
if (configContent && configContent.CUSTOMIZATION) {
|
if (customConfig) {
|
||||||
const customConfig = readConfig(configContent.CUSTOMIZATION);
|
|
||||||
if (customConfig) {
|
|
||||||
console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
|
console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
|
||||||
customConfigJs = `(function (window) {
|
customConfigJs = `(function (window) {
|
||||||
window.__env = window.__env || {};
|
window.__env = window.__env || {};
|
||||||
window.__env.customize = ${customConfig};
|
window.__env.customize = ${customConfig};
|
||||||
}((typeof global !== 'undefined') ? global : this));
|
}((typeof global !== 'undefined') ? global : this));
|
||||||
`;
|
`;
|
||||||
} else {
|
|
||||||
throw new Error('Failed to load customization file');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
|
writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
|
||||||
|
|
||||||
if (currentConfig && currentConfig === newConfig) {
|
if (currentConfig && currentConfig === newConfig) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"TESTNET_ENABLED": false,
|
"TESTNET_ENABLED": false,
|
||||||
|
"TESTNET4_ENABLED": false,
|
||||||
"SIGNET_ENABLED": false,
|
"SIGNET_ENABLED": false,
|
||||||
"LIQUID_ENABLED": false,
|
"LIQUID_ENABLED": false,
|
||||||
"LIQUID_TESTNET_ENABLED": false,
|
"LIQUID_TESTNET_ENABLED": false,
|
||||||
|
398
frontend/package-lock.json
generated
398
frontend/package-lock.json
generated
@ -32,10 +32,10 @@
|
|||||||
"bootstrap": "~4.6.2",
|
"bootstrap": "~4.6.2",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.1",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-echarts": "~17.1.0",
|
"ngx-echarts": "~17.1.0",
|
||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
@ -3197,9 +3197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -3212,9 +3212,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -3227,9 +3227,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3242,9 +3242,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3257,9 +3257,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3272,9 +3272,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3287,9 +3287,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3302,9 +3302,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3317,9 +3317,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -3332,9 +3332,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3347,9 +3347,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -3362,9 +3362,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@ -3377,9 +3377,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@ -3392,9 +3392,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -3407,9 +3407,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -3422,9 +3422,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -3437,9 +3437,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3452,9 +3452,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3467,9 +3467,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3482,9 +3482,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3497,9 +3497,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3512,9 +3512,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -3527,9 +3527,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -8029,9 +8029,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.8.0",
|
"version": "13.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
|
||||||
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -9197,9 +9197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
@ -9208,29 +9208,29 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.1",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.1",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.1",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.1",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.1",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.1",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.1",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.1",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.1",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.1",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.1",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.1",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.1",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.1",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.1",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.1",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.1",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.1",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.1",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.1",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.1",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.1",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild-wasm": {
|
"node_modules/esbuild-wasm": {
|
||||||
@ -20563,141 +20563,141 @@
|
|||||||
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
|
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
|
||||||
},
|
},
|
||||||
"@esbuild/aix-ppc64": {
|
"@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-arm": {
|
"@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-arm64": {
|
"@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-x64": {
|
"@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/darwin-arm64": {
|
"@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/darwin-x64": {
|
"@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/freebsd-arm64": {
|
"@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/freebsd-x64": {
|
"@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-arm": {
|
"@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-arm64": {
|
"@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-ia32": {
|
"@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-loong64": {
|
"@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-mips64el": {
|
"@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-ppc64": {
|
"@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-riscv64": {
|
"@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-s390x": {
|
"@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-x64": {
|
"@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/netbsd-x64": {
|
"@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/openbsd-x64": {
|
"@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/sunos-x64": {
|
"@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-arm64": {
|
"@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-ia32": {
|
"@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-x64": {
|
"@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@eslint-community/eslint-utils": {
|
"@eslint-community/eslint-utils": {
|
||||||
@ -24112,9 +24112,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "13.8.0",
|
"version": "13.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
|
||||||
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^3.0.0",
|
"@cypress/request": "^3.0.0",
|
||||||
@ -25032,33 +25032,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esbuild": {
|
"esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.1",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.1",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.1",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.1",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.1",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.1",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.1",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.1",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.1",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.1",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.1",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.1",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.1",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.1",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.1",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.1",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.1",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.1",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.1",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.1",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.1",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.1",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esbuild-wasm": {
|
"esbuild-wasm": {
|
||||||
|
@ -50,16 +50,16 @@
|
|||||||
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
||||||
"serve:ssr": "npm run generate-config && node server.run.js",
|
"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",
|
"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 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 TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_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 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 TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config",
|
||||||
"prerender": "npm run ng -- run mempool:prerender",
|
"prerender": "npm run ng -- run mempool:prerender",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:run": "cypress run",
|
"cypress:run": "cypress run",
|
||||||
"cypress:run:record": "cypress run --record",
|
"cypress:run:record": "cypress run --record",
|
||||||
"cypress:open:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open",
|
"cypress:open:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open",
|
||||||
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record",
|
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record",
|
||||||
"cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open",
|
"cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open",
|
||||||
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_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 TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_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": "^17.3.1",
|
"@angular-devkit/build-angular": "^17.3.1",
|
||||||
@ -92,7 +92,7 @@
|
|||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
"qrcode": "1.5.1",
|
"qrcode": "1.5.1",
|
||||||
"rxjs": "~7.8.1",
|
"rxjs": "~7.8.1",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.1",
|
||||||
"tinyify": "^4.0.0",
|
"tinyify": "^4.0.0",
|
||||||
"tlite": "^0.1.9",
|
"tlite": "^0.1.9",
|
||||||
"tslib": "~2.6.0",
|
"tslib": "~2.6.0",
|
||||||
@ -115,7 +115,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
|
@ -24,7 +24,7 @@ PROXY_CONFIG = [
|
|||||||
'/api/**', '!/api/v1/ws',
|
'/api/**', '!/api/v1/ws',
|
||||||
'!/liquid', '!/liquid/**', '!/liquid/',
|
'!/liquid', '!/liquid/**', '!/liquid/',
|
||||||
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
|
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
|
||||||
'/testnet/api/**', '/signet/api/**'
|
'/testnet/api/**', '/signet/api/**', '/testnet4/api/**'
|
||||||
],
|
],
|
||||||
target: "https://mempool.space",
|
target: "https://mempool.space",
|
||||||
ws: true,
|
ws: true,
|
||||||
|
@ -168,6 +168,10 @@ let routes: Routes = [
|
|||||||
path: 'testnet',
|
path: 'testnet',
|
||||||
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'testnet4',
|
||||||
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'signet',
|
path: 'signet',
|
||||||
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
|
@ -343,8 +343,8 @@
|
|||||||
<a href="https://opencrypto.org/" title="Coppa - Crypto Open Patent Alliance">
|
<a href="https://opencrypto.org/" title="Coppa - Crypto Open Patent Alliance">
|
||||||
<img class="copa" src="/resources/profile/copa.png" />
|
<img class="copa" src="/resources/profile/copa.png" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://bisq.network/" title="Bisq Network">
|
<a href="https://bitcoin.gob.sv" title="Oficina Nacional del Bitcoin">
|
||||||
<img class="bisq" src="/resources/profile/bisq.svg" />
|
<img class="sv" src="/resources/profile/onbtc-full.svg" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,8 +129,9 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
.bisq {
|
.sv {
|
||||||
top: 3px;
|
height: 85px;
|
||||||
|
width: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -60,7 +60,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -148,7 +148,7 @@ export class AcceleratorDashboardComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
||||||
return this.theme.theme === 'contrast' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1];
|
return this.theme.theme === 'contrast' || this.theme.theme === 'bukele' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -73,7 +73,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
|||||||
if (!this.address || !this.stats) {
|
if (!this.address || !this.stats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (changes.address || changes.isPubkey || changes.addressSummary$) {
|
if (changes.address || changes.isPubkey || changes.addressSummary$ || changes.stats) {
|
||||||
if (this.subscription) {
|
if (this.subscription) {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
@ -248,8 +248,10 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
if (this.subscription) {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -175,6 +175,9 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.transactions = this.tempTransactions;
|
this.transactions = this.tempTransactions;
|
||||||
|
if (this.transactions.length === this.txCount) {
|
||||||
|
this.fullyLoaded = true;
|
||||||
|
}
|
||||||
this.isLoadingTransactions = false;
|
this.isLoadingTransactions = false;
|
||||||
|
|
||||||
if (!this.showBalancePeriod()) {
|
if (!this.showBalancePeriod()) {
|
||||||
|
@ -4,6 +4,8 @@ import { Router, NavigationEnd } from '@angular/router';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { OpenGraphService } from '../../services/opengraph.service';
|
import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ThemeService } from '../../services/theme.service';
|
||||||
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -12,12 +14,12 @@ import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
providers: [NgbTooltipConfig]
|
providers: [NgbTooltipConfig]
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
link: HTMLElement = document.getElementById('canonical');
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public router: Router,
|
public router: Router,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private openGraphService: OpenGraphService,
|
private openGraphService: OpenGraphService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private themeService: ThemeService,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
tooltipConfig: NgbTooltipConfig,
|
tooltipConfig: NgbTooltipConfig,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
@ -52,11 +54,7 @@ export class AppComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.router.events.subscribe((val) => {
|
this.router.events.subscribe((val) => {
|
||||||
if (val instanceof NavigationEnd) {
|
if (val instanceof NavigationEnd) {
|
||||||
let domain = 'mempool.space';
|
this.seoService.updateCanonical(this.location.path());
|
||||||
if (this.stateService.env.BASE_MODULE === 'liquid') {
|
|
||||||
domain = 'liquid.network';
|
|
||||||
}
|
|
||||||
this.link.setAttribute('href', 'https://' + domain + this.location.path());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -57,8 +57,9 @@ export class BalanceWidgetComponent implements OnInit, OnChanges {
|
|||||||
calculateStats(summary: AddressTxSummary[]): void {
|
calculateStats(summary: AddressTxSummary[]): void {
|
||||||
let weekTotal = 0;
|
let weekTotal = 0;
|
||||||
let monthTotal = 0;
|
let monthTotal = 0;
|
||||||
const weekAgo = (Date.now() / 1000) - (60 * 60 * 24 * 7);
|
|
||||||
const monthAgo = (Date.now() / 1000) - (60 * 60 * 24 * 30);
|
const weekAgo = (new Date(new Date().setHours(0, 0, 0, 0) - (7 * 24 * 60 * 60 * 1000)).getTime()) / 1000;
|
||||||
|
const monthAgo = (new Date(new Date().setHours(0, 0, 0, 0) - (30 * 24 * 60 * 60 * 1000)).getTime()) / 1000;
|
||||||
for (let i = 0; i < summary.length && summary[i].time >= monthAgo; i++) {
|
for (let i = 0; i < summary.length && summary[i].time >= monthAgo; i++) {
|
||||||
monthTotal += summary[i].value;
|
monthTotal += summary[i].value;
|
||||||
if (summary[i].time >= weekAgo) {
|
if (summary[i].time >= weekAgo) {
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
<app-indexing-progress></app-indexing-progress>
|
||||||
|
|
||||||
|
<div class="full-container">
|
||||||
|
<div class="card-header mb-0 mb-md-4">
|
||||||
|
<div class="d-flex d-md-block align-items-baseline">
|
||||||
|
<span i18n="mining.block-fees-subsidy-subsidy">Block Fees Vs Subsidy</span>
|
||||||
|
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
||||||
|
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 432" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
|
||||||
|
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 3D
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 1008" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
|
||||||
|
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 1W
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||||
|
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||||
|
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
||||||
|
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 6M
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 52560" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
|
||||||
|
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 1Y
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 105120" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
|
||||||
|
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 2Y
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 157680" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
|
||||||
|
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> 3Y
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
||||||
|
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]" formControlName="dateSpan"> ALL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" [style]="{opacity: isLoading ? 0.5 : 1}"
|
||||||
|
(chartInit)="onChartInit($event)">
|
||||||
|
</div>
|
||||||
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,66 @@
|
|||||||
|
.card-header {
|
||||||
|
border-bottom: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
@media (min-width: 465px) {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
position: relative;
|
||||||
|
color: #ffffff91;
|
||||||
|
margin-top: -13px;
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0px 15px;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 225px);
|
||||||
|
min-height: 400px;
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
height: calc(100vh - 150px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-right: 10px;
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
padding-bottom: 25px;
|
||||||
|
}
|
||||||
|
@media (max-width: 829px) {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
padding-bottom: 25px;
|
||||||
|
}
|
||||||
|
@media (max-width: 629px) {
|
||||||
|
padding-bottom: 55px;
|
||||||
|
}
|
||||||
|
@media (max-width: 567px) {
|
||||||
|
padding-bottom: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chart-widget {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 270px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -0,0 +1,510 @@
|
|||||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
|
||||||
|
import { EChartsOption } from '../../graphs/echarts';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { catchError, map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
import { formatNumber } from '@angular/common';
|
||||||
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
|
import { download, formatterXAxis } from '../../shared/graphs.utils';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||||
|
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { MiningService } from '../../services/mining.service';
|
||||||
|
import { StorageService } from '../../services/storage.service';
|
||||||
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-block-fees-subsidy-graph',
|
||||||
|
templateUrl: './block-fees-subsidy-graph.component.html',
|
||||||
|
styleUrls: ['./block-fees-subsidy-graph.component.scss'],
|
||||||
|
styles: [`
|
||||||
|
.loadingGraphs {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(50% - 15px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
`],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class BlockFeesSubsidyGraphComponent implements OnInit {
|
||||||
|
@Input() right: number | string = 45;
|
||||||
|
@Input() left: number | string = 75;
|
||||||
|
|
||||||
|
miningWindowPreference: string;
|
||||||
|
radioGroupForm: UntypedFormGroup;
|
||||||
|
|
||||||
|
chartOptions: EChartsOption = {};
|
||||||
|
chartInitOptions = {
|
||||||
|
renderer: 'svg',
|
||||||
|
};
|
||||||
|
|
||||||
|
statsObservable$: Observable<any>;
|
||||||
|
data: any;
|
||||||
|
subsidies: { [key: number]: number } = {};
|
||||||
|
isLoading = true;
|
||||||
|
formatNumber = formatNumber;
|
||||||
|
timespan = '';
|
||||||
|
chartInstance: any = undefined;
|
||||||
|
showFiat = false;
|
||||||
|
updateZoom = false;
|
||||||
|
zoomSpan = 100;
|
||||||
|
zoomTimeSpan = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private formBuilder: UntypedFormBuilder,
|
||||||
|
public stateService: StateService,
|
||||||
|
private storageService: StorageService,
|
||||||
|
private miningService: MiningService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private zone: NgZone,
|
||||||
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
|
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
||||||
|
|
||||||
|
this.subsidies = this.initSubsidies();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.seoService.setTitle($localize`:@@mining.block-fees-subsidy:Block Fees Vs Subsidy`);
|
||||||
|
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fees-subsidy:See the mining fees earned per Bitcoin block compared to the Bitcoin block subsidy, visualized in BTC and USD over time.`);
|
||||||
|
|
||||||
|
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
|
||||||
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
|
|
||||||
|
this.route
|
||||||
|
.fragment
|
||||||
|
.subscribe((fragment) => {
|
||||||
|
if (['24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
|
||||||
|
.pipe(
|
||||||
|
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||||
|
switchMap((timespan) => {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.storageService.setValue('miningWindowPreference', timespan);
|
||||||
|
this.timespan = timespan;
|
||||||
|
this.zoomTimeSpan = timespan;
|
||||||
|
this.isLoading = true;
|
||||||
|
return this.apiService.getHistoricalBlockFees$(timespan)
|
||||||
|
.pipe(
|
||||||
|
tap((response) => {
|
||||||
|
this.data = {
|
||||||
|
timestamp: response.body.map(val => val.timestamp * 1000),
|
||||||
|
blockHeight: response.body.map(val => val.avgHeight),
|
||||||
|
blockFees: response.body.map(val => val.avgFees / 100_000_000),
|
||||||
|
blockFeesFiat: response.body.filter(val => val['USD'] > 0).map(val => val.avgFees / 100_000_000 * val['USD']),
|
||||||
|
blockSubsidy: response.body.map(val => this.subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000),
|
||||||
|
blockSubsidyFiat: response.body.filter(val => val['USD'] > 0).map(val => this.subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000 * val['USD']),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prepareChartOptions();
|
||||||
|
this.isLoading = false;
|
||||||
|
}),
|
||||||
|
map((response) => {
|
||||||
|
return {
|
||||||
|
blockCount: parseInt(response.headers.get('x-total-count'), 10),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
share()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareChartOptions() {
|
||||||
|
let title: object;
|
||||||
|
if (this.data.blockFees.length === 0) {
|
||||||
|
title = {
|
||||||
|
textStyle: {
|
||||||
|
color: 'grey',
|
||||||
|
fontSize: 15
|
||||||
|
},
|
||||||
|
text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`,
|
||||||
|
left: 'center',
|
||||||
|
top: 'center'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartOptions = {
|
||||||
|
title: title,
|
||||||
|
color: [
|
||||||
|
'#ff9f00',
|
||||||
|
'#0aab2f',
|
||||||
|
],
|
||||||
|
animation: false,
|
||||||
|
grid: {
|
||||||
|
top: 80,
|
||||||
|
bottom: 80,
|
||||||
|
right: this.right,
|
||||||
|
left: this.left,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: !this.isMobile(),
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line'
|
||||||
|
},
|
||||||
|
backgroundColor: 'color-mix(in srgb, var(--active-bg) 95%, transparent)',
|
||||||
|
borderRadius: 4,
|
||||||
|
shadowColor: 'color-mix(in srgb, var(--active-bg) 95%, transparent)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'var(--tooltip-grey)',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
borderColor: 'var(--active-bg)',
|
||||||
|
formatter: function (data) {
|
||||||
|
if (data.length <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.zoomTimeSpan, parseInt(this.data.timestamp[data[0].dataIndex], 10))}</b><br>`;
|
||||||
|
for (let i = data.length - 1; i >= 0; i--) {
|
||||||
|
const tick = data[i];
|
||||||
|
if (!this.showFiat) tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data, this.locale, '1.0-3')} BTC<br>`;
|
||||||
|
else tooltip += `${tick.marker} ${tick.seriesName}: ${this.fiatCurrencyPipe.transform(tick.data, null, 'USD') }<br>`;
|
||||||
|
}
|
||||||
|
if (!this.showFiat) tooltip += `<div style="margin-left: 2px">${formatNumber(data.reduce((acc, val) => acc + val.data, 0), this.locale, '1.0-3')} BTC</div>`;
|
||||||
|
else tooltip += `<div style="margin-left: 2px">${this.fiatCurrencyPipe.transform(data.reduce((acc, val) => acc + val.data, 0), null, 'USD')}</div>`;
|
||||||
|
if (['24h', '3d'].includes(this.zoomTimeSpan)) {
|
||||||
|
tooltip += `<small>` + $localize`At block <b style="color: white; margin-left: 2px">${data[0].axisValue}` + `</small>`;
|
||||||
|
} else {
|
||||||
|
tooltip += `<small>` + $localize`Around block <b style="color: white; margin-left: 2px">${data[0].axisValue}` + `</small>`;
|
||||||
|
}
|
||||||
|
return tooltip;
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
xAxis: this.data.blockFees.length === 0 ? undefined : [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: this.data.blockHeight,
|
||||||
|
show: false,
|
||||||
|
axisLabel: {
|
||||||
|
hideOverlap: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: this.data.timestamp,
|
||||||
|
show: true,
|
||||||
|
position: 'bottom',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'var(--grey)',
|
||||||
|
formatter: (val) => {
|
||||||
|
return formatterXAxis(this.locale, this.timespan, parseInt(val, 10));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
legend: this.data.blockFees.length === 0 ? undefined : {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'Subsidy',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fees',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Subsidy (USD)',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fees (USD)',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selected: {
|
||||||
|
'Subsidy (USD)': this.showFiat,
|
||||||
|
'Fees (USD)': this.showFiat,
|
||||||
|
'Subsidy': !this.showFiat,
|
||||||
|
'Fees': !this.showFiat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: this.data.blockFees.length === 0 ? undefined : [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'var(--grey)',
|
||||||
|
formatter: (val) => {
|
||||||
|
return `${val} BTC`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dotted',
|
||||||
|
color: 'var(--transparent-fg)',
|
||||||
|
opacity: 0.25,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'var(--grey)',
|
||||||
|
formatter: function(val) {
|
||||||
|
return this.fiatShortenerPipe.transform(val, null, 'USD');
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: this.data.blockFees.length === 0 ? undefined : [
|
||||||
|
{
|
||||||
|
name: 'Subsidy',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: this.data.blockSubsidy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fees',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: this.data.blockFees,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Subsidy (USD)',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: this.data.blockSubsidyFiat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fees (USD)',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: this.data.blockFeesFiat,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataZoom: this.data.blockFees.length === 0 ? undefined : [{
|
||||||
|
type: 'inside',
|
||||||
|
realtime: true,
|
||||||
|
zoomLock: true,
|
||||||
|
maxSpan: 100,
|
||||||
|
minSpan: 1,
|
||||||
|
moveOnMouseMove: false,
|
||||||
|
}, {
|
||||||
|
showDetail: false,
|
||||||
|
show: true,
|
||||||
|
type: 'slider',
|
||||||
|
brushSelect: false,
|
||||||
|
realtime: true,
|
||||||
|
left: 20,
|
||||||
|
right: 15,
|
||||||
|
selectedDataBackground: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.45,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onChartInit(ec) {
|
||||||
|
this.chartInstance = ec;
|
||||||
|
|
||||||
|
this.chartInstance.on('legendselectchanged', (params) => {
|
||||||
|
const isFiat = params.name.includes('USD');
|
||||||
|
if (isFiat === this.showFiat) return;
|
||||||
|
|
||||||
|
const isActivation = params.selected[params.name];
|
||||||
|
if (isFiat === isActivation) {
|
||||||
|
this.showFiat = true;
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Subsidy' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Fees' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Subsidy (USD)' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Fees (USD)' });
|
||||||
|
} else {
|
||||||
|
this.showFiat = false;
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Subsidy' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Fees' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Subsidy (USD)' });
|
||||||
|
this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Fees (USD)' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.chartInstance.on('datazoom', (params) => {
|
||||||
|
if (params.silent || this.isLoading || ['24h', '3d'].includes(this.timespan)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateZoom = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.chartInstance.on('click', (e) => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
if (['24h', '3d'].includes(this.zoomTimeSpan)) {
|
||||||
|
const url = new RelativeUrlPipe(this.stateService).transform(`/block/${e.name}`);
|
||||||
|
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
|
||||||
|
window.open(url);
|
||||||
|
} else {
|
||||||
|
this.router.navigate([url]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:pointerup', ['$event'])
|
||||||
|
onPointerUp(event: PointerEvent) {
|
||||||
|
if (this.updateZoom) {
|
||||||
|
this.onZoom();
|
||||||
|
this.updateZoom = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobile() {
|
||||||
|
return (window.innerWidth <= 767.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
initSubsidies(): { [key: number]: number } {
|
||||||
|
let blockReward = 50 * 100_000_000;
|
||||||
|
const subsidies = {};
|
||||||
|
for (let i = 0; i <= 33; i++) {
|
||||||
|
subsidies[i] = blockReward;
|
||||||
|
blockReward = Math.floor(blockReward / 2);
|
||||||
|
}
|
||||||
|
return subsidies;
|
||||||
|
}
|
||||||
|
|
||||||
|
onZoom() {
|
||||||
|
const option = this.chartInstance.getOption();
|
||||||
|
const timestamps = option.xAxis[1].data;
|
||||||
|
const startTimestamp = timestamps[option.dataZoom[0].startValue];
|
||||||
|
const endTimestamp = timestamps[option.dataZoom[0].endValue];
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.cd.detectChanges();
|
||||||
|
|
||||||
|
const subscription = this.apiService.getBlockFeesFromTimespan$(Math.floor(startTimestamp / 1000), Math.floor(endTimestamp / 1000))
|
||||||
|
.pipe(
|
||||||
|
tap((response) => {
|
||||||
|
const startIndex = option.dataZoom[0].startValue;
|
||||||
|
const endIndex = option.dataZoom[0].endValue;
|
||||||
|
|
||||||
|
// Update series with more granular data
|
||||||
|
const lengthBefore = this.data.timestamp.length;
|
||||||
|
this.data.timestamp.splice(startIndex, endIndex - startIndex, ...response.body.map(val => val.timestamp * 1000));
|
||||||
|
this.data.blockHeight.splice(startIndex, endIndex - startIndex, ...response.body.map(val => val.avgHeight));
|
||||||
|
this.data.blockFees.splice(startIndex, endIndex - startIndex, ...response.body.map(val => val.avgFees / 100_000_000));
|
||||||
|
this.data.blockFeesFiat.splice(startIndex, endIndex - startIndex, ...response.body.filter(val => val['USD'] > 0).map(val => val.avgFees / 100_000_000 * val['USD']));
|
||||||
|
this.data.blockSubsidy.splice(startIndex, endIndex - startIndex, ...response.body.map(val => this.subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000));
|
||||||
|
this.data.blockSubsidyFiat.splice(startIndex, endIndex - startIndex, ...response.body.filter(val => val['USD'] > 0).map(val => this.subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000 * val['USD']));
|
||||||
|
option.series[0].data = this.data.blockSubsidy;
|
||||||
|
option.series[1].data = this.data.blockFees;
|
||||||
|
option.series[2].data = this.data.blockSubsidyFiat;
|
||||||
|
option.series[3].data = this.data.blockFeesFiat;
|
||||||
|
option.xAxis[0].data = this.data.blockHeight;
|
||||||
|
option.xAxis[1].data = this.data.timestamp;
|
||||||
|
this.chartInstance.setOption(option, true);
|
||||||
|
const lengthAfter = this.data.timestamp.length;
|
||||||
|
|
||||||
|
// Update the zoom to keep the same range after the update
|
||||||
|
this.chartInstance.dispatchAction({
|
||||||
|
type: 'dataZoom',
|
||||||
|
startValue: startIndex,
|
||||||
|
endValue: endIndex + lengthAfter - lengthBefore,
|
||||||
|
silent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the chart
|
||||||
|
const newOption = this.chartInstance.getOption();
|
||||||
|
this.zoomSpan = newOption.dataZoom[0].end - newOption.dataZoom[0].start;
|
||||||
|
this.zoomTimeSpan = this.getTimeRangeFromTimespan(Math.floor(this.data.timestamp[newOption.dataZoom[0].startValue] / 1000), Math.floor(this.data.timestamp[newOption.dataZoom[0].endValue] / 1000));
|
||||||
|
this.isLoading = false;
|
||||||
|
}),
|
||||||
|
catchError(() => {
|
||||||
|
const newOption = this.chartInstance.getOption();
|
||||||
|
this.zoomSpan = newOption.dataZoom[0].end - newOption.dataZoom[0].start;
|
||||||
|
this.zoomTimeSpan = this.getTimeRangeFromTimespan(Math.floor(this.data.timestamp[newOption.dataZoom[0].startValue] / 1000), Math.floor(this.data.timestamp[newOption.dataZoom[0].endValue] / 1000));
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cd.detectChanges();
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
).subscribe(() => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
this.cd.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeRangeFromTimespan(from: number, to: number): string {
|
||||||
|
const timespan = to - from;
|
||||||
|
switch (true) {
|
||||||
|
case timespan >= 3600 * 24 * 365 * 4: return 'all';
|
||||||
|
case timespan >= 3600 * 24 * 365 * 3: return '4y';
|
||||||
|
case timespan >= 3600 * 24 * 365 * 2: return '3y';
|
||||||
|
case timespan >= 3600 * 24 * 365: return '2y';
|
||||||
|
case timespan >= 3600 * 24 * 30 * 6: return '1y';
|
||||||
|
case timespan >= 3600 * 24 * 30 * 3: return '6m';
|
||||||
|
case timespan >= 3600 * 24 * 30: return '3m';
|
||||||
|
case timespan >= 3600 * 24 * 7: return '1m';
|
||||||
|
case timespan >= 3600 * 24 * 3: return '1w';
|
||||||
|
case timespan >= 3600 * 24: return '3d';
|
||||||
|
default: return '24h';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveChart() {
|
||||||
|
// @ts-ignore
|
||||||
|
const prevBottom = this.chartOptions.grid.bottom;
|
||||||
|
const now = new Date();
|
||||||
|
// @ts-ignore
|
||||||
|
this.chartOptions.grid.bottom = 40;
|
||||||
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
|
download(this.chartInstance.getDataURL({
|
||||||
|
pixelRatio: 2,
|
||||||
|
excludeComponents: ['dataZoom'],
|
||||||
|
}), `block-fees-subsidy-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||||
|
// @ts-ignore
|
||||||
|
this.chartOptions.grid.bottom = prevBottom;
|
||||||
|
this.chartOptions.backgroundColor = 'none';
|
||||||
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -651,13 +651,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||||
return (tx: TxView) => {
|
return (tx: TxView) => {
|
||||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||||
if (this.themeService.theme !== 'contrast') {
|
if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') {
|
||||||
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
} else {
|
} else {
|
||||||
return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000));
|
return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.themeService.theme !== 'contrast') {
|
if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') {
|
||||||
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
||||||
tx,
|
tx,
|
||||||
defaultColors.unmatchedfee,
|
defaultColors.unmatchedfee,
|
||||||
|
@ -69,7 +69,7 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
||||||
this.theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
this.theme.theme === 'contrast' || this.theme.theme === 'bukele' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
this.updateAllColors();
|
this.updateAllColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ export default class BlockScene {
|
|||||||
this.flip = flip;
|
this.flip = flip;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
this.highlightingEnabled = highlighting;
|
this.highlightingEnabled = highlighting;
|
||||||
theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
theme.theme === 'contrast' || theme.theme === 'bukele' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
|
@ -177,7 +177,7 @@ export function ageColorFunction(
|
|||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = theme !== 'contrast' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
const color = theme !== 'contrast' && theme !== 'bukele' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
||||||
|
|
||||||
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.block-overview-tooltip {
|
.block-overview-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: var(--tooltip-grey);
|
color: var(--tooltip-grey);
|
||||||
@ -30,7 +30,7 @@ th, td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge.badge-accelerated {
|
.badge.badge-accelerated {
|
||||||
background-color: var(--tertiary);
|
background-color: #653b9c;
|
||||||
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
||||||
color: white;
|
color: white;
|
||||||
animation: acceleratePulse 1s infinite;
|
animation: acceleratePulse 1s infinite;
|
||||||
@ -71,7 +71,7 @@ th, td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes acceleratePulse {
|
@keyframes acceleratePulse {
|
||||||
0% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
||||||
100% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
100% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
}
|
}
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -136,7 +136,12 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
|||||||
return of(transactions);
|
return of(transactions);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500
|
||||||
|
? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
|
||||||
|
.pipe(catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}))
|
||||||
|
: of([])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -345,7 +345,12 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500
|
||||||
|
? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
|
||||||
|
.pipe(catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}))
|
||||||
|
: of([])
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
.fee-span {
|
.fee-span {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: #fff000;
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-count {
|
.transaction-count {
|
||||||
@ -130,7 +130,7 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-bottom: calc(var(--block-size) * 0.3) solid #FFF;
|
border-bottom: calc(var(--block-size) * 0.3) solid var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flashing {
|
.flashing {
|
||||||
|
@ -350,7 +350,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return {
|
return {
|
||||||
left: addLeft + this.blockOffset * index + 'px',
|
left: addLeft + this.blockOffset * index + 'px',
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
var(--secondary),
|
||||||
var(--secondary) ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
${this.gradientColors[this.network][1]} 100%
|
${this.gradientColors[this.network][1]} 100%
|
||||||
@ -362,7 +362,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
convertStyleForLoadingBlock(style) {
|
convertStyleForLoadingBlock(style) {
|
||||||
return {
|
return {
|
||||||
...style,
|
...style,
|
||||||
background: "#2d3348",
|
background: "var(--secondary)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +371,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
left: addLeft + (this.blockOffset * index) + 'px',
|
left: addLeft + (this.blockOffset * index) + 'px',
|
||||||
background: "#2d3348",
|
background: "var(--secondary)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-toggle {
|
.time-toggle {
|
||||||
color: white;
|
color: var(--fg);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -1.8em;
|
bottom: -1.8em;
|
||||||
@ -68,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-display-toggle {
|
.block-display-toggle {
|
||||||
color: white;
|
color: var(--fg);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 15.8em;
|
bottom: 15.8em;
|
||||||
|
@ -32,12 +32,12 @@ export class ClockComponent implements OnInit {
|
|||||||
limitHeight: number;
|
limitHeight: number;
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0'],
|
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||||
liquid: ['#116761', '#183550'],
|
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||||
'liquidtestnet': ['#494a4a', '#272e46'],
|
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||||
testnet: ['#1d486f', '#183550'],
|
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
testnet4: ['#1d486f', '#183550'],
|
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
signet: ['#6f1d5d', '#471850'],
|
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -100,8 +100,8 @@ export class ClockComponent implements OnInit {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
var(--secondary),
|
||||||
#2d3348 ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
${this.gradientColors[''][1]} 100%
|
${this.gradientColors[''][1]} 100%
|
||||||
)`,
|
)`,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@for (widget of widgets; track widget.component) {
|
@for (widget of widgets; track widget.component) {
|
||||||
@switch (widget.component) {
|
@switch (widget.component) {
|
||||||
@case ('fees') {
|
@case ('fees') {
|
||||||
<div class="col card-wrapper">
|
<div class="col card-wrapper" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<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">
|
||||||
<div class="card-body less-padding">
|
<div class="card-body less-padding">
|
||||||
@ -14,12 +14,12 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('difficulty') {
|
@case ('difficulty') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<app-difficulty></app-difficulty>
|
<app-difficulty></app-difficulty>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('goggles') {
|
@case ('goggles') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
||||||
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('incoming') {
|
@case ('incoming') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<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>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('replacements') {
|
@case ('replacements') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
||||||
@ -140,7 +140,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('blocks') {
|
@case ('blocks') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||||
@ -184,7 +184,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('transactions') {
|
@case ('transactions') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title" i18n="dashboard.recent-transactions">Recent Transactions</h5>
|
<h5 class="card-title" i18n="dashboard.recent-transactions">Recent Transactions</h5>
|
||||||
@ -224,13 +224,13 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('balance') {
|
@case ('balance') {
|
||||||
<div class="col card-wrapper">
|
<div class="col card-wrapper" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="main-title" i18n="dashboard.treasury">Treasury</div>
|
<div class="main-title" i18n="dashboard.treasury">Treasury</div>
|
||||||
<app-balance-widget [address]="widget.props.address" [addressSummary$]="addressSummary$" [addressInfo]="address"></app-balance-widget>
|
<app-balance-widget [address]="widget.props.address" [addressSummary$]="addressSummary$" [addressInfo]="address"></app-balance-widget>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('address') {
|
@case ('address') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
||||||
@ -238,13 +238,13 @@
|
|||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address?.chain_stats" [widget]="true" [height]="graphHeight"></app-address-graph>
|
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address ? address.chain_stats : null" [widget]="true" [height]="graphHeight"></app-address-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('addressTransactions') {
|
@case ('addressTransactions') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
||||||
@ -257,6 +257,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@case ('twitter') {
|
||||||
|
<div class="col" style="min-height:410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
|
<div class="card graph-card">
|
||||||
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2 d-flex flex-column">
|
||||||
|
<a class="title-link" [href]="'https://x.com/' + widget.props?.handle" target="_blank">
|
||||||
|
<h5 class="card-title d-inline" i18n="dashboard.x-timeline">X Timeline</h5>
|
||||||
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
|
@defer {
|
||||||
|
<app-twitter-widget [handle]="widget.props?.handle" style="flex-grow: 1"></app-twitter-widget>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs';
|
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs';
|
||||||
import { catchError, filter, map, scan, share, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
|
import { catchError, filter, map, scan, share, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { BlockExtended, OptimizedMempoolStats, TransactionStripped } from '../../interfaces/node-api.interface';
|
import { BlockExtended, OptimizedMempoolStats, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
@ -57,6 +57,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
incomingGraphHeight: number = 300;
|
incomingGraphHeight: number = 300;
|
||||||
graphHeight: number = 300;
|
graphHeight: number = 300;
|
||||||
webGlEnabled = true;
|
webGlEnabled = true;
|
||||||
|
isMobile: boolean = window.innerWidth <= 767.98;
|
||||||
|
|
||||||
widgets;
|
widgets;
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object,
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
@ -230,8 +232,10 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
this.stateService.live2Chart$
|
this.stateService.live2Chart$
|
||||||
.pipe(
|
.pipe(
|
||||||
scan((acc, stats) => {
|
scan((acc, stats) => {
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
const start = now - (2 * 60 * 60);
|
||||||
acc.unshift(stats);
|
acc.unshift(stats);
|
||||||
acc = acc.slice(0, 120);
|
acc = acc.filter(p => p.added >= start);
|
||||||
return acc;
|
return acc;
|
||||||
}, (mempoolStats || []))
|
}, (mempoolStats || []))
|
||||||
),
|
),
|
||||||
@ -283,8 +287,8 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
|
|
||||||
startAddressSubscription(): void {
|
startAddressSubscription(): void {
|
||||||
if (this.stateService.env.customize && this.stateService.env.customize.dashboard.widgets.some(w => w.props?.address)) {
|
if (this.stateService.env.customize && this.stateService.env.customize.dashboard.widgets.some(w => w.props?.address)) {
|
||||||
const address = this.stateService.env.customize.dashboard.widgets.find(w => w.props?.address).props.address;
|
let addressString = this.stateService.env.customize.dashboard.widgets.find(w => w.props?.address).props.address;
|
||||||
const addressString = (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(address)) ? address.toLowerCase() : address;
|
addressString = (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(addressString)) ? addressString.toLowerCase() : addressString;
|
||||||
|
|
||||||
this.addressSubscription = (
|
this.addressSubscription = (
|
||||||
addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
|
addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
|
||||||
@ -299,6 +303,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
).subscribe((address: Address) => {
|
).subscribe((address: Address) => {
|
||||||
this.websocketService.startTrackAddress(address.address);
|
this.websocketService.startTrackAddress(address.address);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addressSummary$ = (
|
this.addressSummary$ = (
|
||||||
@ -368,5 +373,6 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
this.goggleResolution = 86;
|
this.goggleResolution = 86;
|
||||||
this.graphHeight = 310;
|
this.graphHeight = 310;
|
||||||
}
|
}
|
||||||
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
.difficulty-tooltip {
|
.difficulty-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
<linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" stop-color="#105fb0" />
|
<stop offset="0%" stop-color="var(--primary)" />
|
||||||
<stop offset="100%" stop-color="#9339f4" />
|
<stop offset="100%" stop-color="var(--mainnet-alt)" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="diff-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
<linearGradient id="diff-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" stop-color="#2486eb" />
|
<stop offset="0%" stop-color="#2486eb" />
|
||||||
|
@ -128,7 +128,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -223,7 +224,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.background {
|
.background {
|
||||||
background: linear-gradient(to right, var(--primary), #9339f4);
|
background: linear-gradient(to right, var(--primary), var(--mainnet-alt));
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
91
frontend/src/app/components/faucet/faucet.component.html
Normal file
91
frontend/src/app/components/faucet/faucet.component.html
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<div class="title-block justify-content-center">
|
||||||
|
<h1 i18n="testnet4.faucet">Testnet4 Faucet</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faucet-container text-center">
|
||||||
|
|
||||||
|
@if (txid) {
|
||||||
|
<div class="alert alert-success w-100 text-truncate">
|
||||||
|
<fa-icon [icon]="['fas', 'circle-check']"></fa-icon>
|
||||||
|
Sent!
|
||||||
|
<a class="text-primary" [href]="'/testnet4/tx/' + txid">{{ txid }}</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@else if (loading) {
|
||||||
|
<p>Loading faucet...</p>
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
} @else if (!user) {
|
||||||
|
<!-- User not logged in -->
|
||||||
|
<div class="alert alert-mempool d-block text-center w-100">
|
||||||
|
<div class="d-inline align-middle">
|
||||||
|
<span>To limit abuse, </span>
|
||||||
|
<a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">authenticate </a>
|
||||||
|
<span class="mr-2">or</span>
|
||||||
|
</div>
|
||||||
|
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@else if (error === 'not_available') {
|
||||||
|
<!-- User logged in but not a paid user or did not link its Twitter account -->
|
||||||
|
<div class="alert alert-mempool d-block text-center w-100">
|
||||||
|
<div class="d-inline align-middle">
|
||||||
|
<span class="mb-2 mr-2">To limit abuse</span>
|
||||||
|
</div>
|
||||||
|
<app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if (error) {
|
||||||
|
<!-- User can request -->
|
||||||
|
<app-mempool-error class="w-100" [error]="error"></app-mempool-error>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!loading) {
|
||||||
|
<form [formGroup]="faucetForm" class="formGroup" (submit)="requestCoins()" [style]="(error || !this.user) ? 'opacity: 0.3; pointer-events: none' : ''">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-0">
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text" i18n="amount-sats">Amount (sats)</span>
|
||||||
|
</div>
|
||||||
|
<input type="number" class="form-control" [class]="{invalid: invalidAmount}" formControlName="satoshis" id="satoshis">
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="btn btn-secondary" (click)="setAmount(5000)">5k</button>
|
||||||
|
<button type="button" class="btn btn-secondary ml-2" (click)="setAmount(50000)">50k</button>
|
||||||
|
<button type="button" class="btn btn-secondary ml-2" (click)="setAmount(500000)">500k</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-danger text-left" *ngIf="invalidAmount">
|
||||||
|
<div *ngIf="amount?.errors?.['required']">Amount is required</div>
|
||||||
|
<div *ngIf="amount?.errors?.['min']">Minimum is {{ amount?.errors?.['min'].min | number }} tSats</div>
|
||||||
|
<div *ngIf="amount?.errors?.['max']">Maximum is {{ amount?.errors?.['max'].max | number }} tSats</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-lg mt-2">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text" i18n="address">Address</span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" [class]="{invalid: invalidAddress}" formControlName="address" id="address" placeholder="tb1q...">
|
||||||
|
<button type="submit" class="btn btn-primary submit-button" [disabled]="!faucetForm.valid || !faucetForm.get('address')?.dirty" i18n="testnet4.request-coins">Request Testnet4 Coins</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-danger text-left" *ngIf="invalidAddress">
|
||||||
|
<div *ngIf="address?.errors?.['required']">Address is required</div>
|
||||||
|
<div *ngIf="address?.errors?.['pattern']">Must be a valid testnet4 address</div>
|
||||||
|
<div *ngIf="address?.errors?.['forbiddenAddress']">You cannot use this address</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Send back coins -->
|
||||||
|
@if (status?.address) {
|
||||||
|
<div class="mt-2 alert alert-info w-100">If you no longer need your testnet4 coins, please consider <a class="text-primary" [routerLink]="['/address/' | relativeUrl, status.address]"><u>sending them back</u></a> to replenish the faucet.</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
52
frontend/src/app/components/faucet/faucet.component.scss
Normal file
52
frontend/src/app/components/faucet/faucet.component.scss
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
.formGroup {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-end;
|
||||||
|
row-gap: 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
min-width: 160px;
|
||||||
|
flex-grow: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button, .button-group, .button-group .btn {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.submit-button:disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#satoshis::after {
|
||||||
|
content: 'sats';
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5em;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faucet-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: var(--red);
|
||||||
|
}
|
155
frontend/src/app/components/faucet/faucet.component.ts
Normal file
155
frontend/src/app/components/faucet/faucet.component.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from "@angular/core";
|
||||||
|
import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { StorageService } from "../../services/storage.service";
|
||||||
|
import { ServicesApiServices } from "../../services/services-api.service";
|
||||||
|
import { getRegex } from "../../shared/regex.utils";
|
||||||
|
import { StateService } from "../../services/state.service";
|
||||||
|
import { WebsocketService } from "../../services/websocket.service";
|
||||||
|
import { AudioService } from "../../services/audio.service";
|
||||||
|
import { HttpErrorResponse } from "@angular/common/http";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-faucet',
|
||||||
|
templateUrl: './faucet.component.html',
|
||||||
|
styleUrls: ['./faucet.component.scss']
|
||||||
|
})
|
||||||
|
export class FaucetComponent implements OnInit, OnDestroy {
|
||||||
|
loading = true;
|
||||||
|
error: string = '';
|
||||||
|
user: any = undefined;
|
||||||
|
txid: string = '';
|
||||||
|
|
||||||
|
faucetStatusSubscription: Subscription;
|
||||||
|
status: {
|
||||||
|
min: number; // minimum amount to request at once (in sats)
|
||||||
|
max: number; // maximum amount to request at once
|
||||||
|
address?: string; // faucet address
|
||||||
|
code: 'ok' | 'faucet_not_available' | 'faucet_maximum_reached' | 'faucet_too_soon';
|
||||||
|
} | null = null;
|
||||||
|
faucetForm: FormGroup;
|
||||||
|
|
||||||
|
mempoolPositionSubscription: Subscription;
|
||||||
|
confirmationSubscription: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
private storageService: StorageService,
|
||||||
|
private servicesApiService: ServicesApiServices,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private audioService: AudioService
|
||||||
|
) {
|
||||||
|
this.faucetForm = this.formBuilder.group({
|
||||||
|
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4'))]],
|
||||||
|
'satoshis': [0, [Validators.required, Validators.min(0), Validators.max(0)]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.stateService.markBlock$.next({});
|
||||||
|
this.websocketService.stopTrackingTransaction();
|
||||||
|
if (this.mempoolPositionSubscription) {
|
||||||
|
this.mempoolPositionSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.confirmationSubscription) {
|
||||||
|
this.confirmationSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.user = this.storageService.getAuth()?.user ?? null;
|
||||||
|
if (!this.user) {
|
||||||
|
this.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup form
|
||||||
|
this.faucetStatusSubscription = this.servicesApiService.getFaucetStatus$().subscribe({
|
||||||
|
next: (status) => {
|
||||||
|
if (!status) {
|
||||||
|
this.error = 'internal_server_error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.status = status;
|
||||||
|
|
||||||
|
const notFaucetAddressValidator = (faucetAddress: string): ValidatorFn => {
|
||||||
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
|
const forbidden = control.value === faucetAddress;
|
||||||
|
return forbidden ? { forbiddenAddress: { value: control.value } } : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.faucetForm = this.formBuilder.group({
|
||||||
|
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), notFaucetAddressValidator(this.status.address)]],
|
||||||
|
'satoshis': [this.status.min, [Validators.required, Validators.min(this.status.min), Validators.max(this.status.max)]]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.status.code !== 'ok') {
|
||||||
|
this.error = this.status.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.error = response.error;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track transaction
|
||||||
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
|
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
|
||||||
|
if (txPosition && txPosition.txid === this.txid) {
|
||||||
|
this.stateService.markBlock$.next({
|
||||||
|
txid: txPosition.txid,
|
||||||
|
mempoolPosition: txPosition.position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.confirmationSubscription = this.stateService.txConfirmed$.subscribe(([txConfirmed, block]) => {
|
||||||
|
if (txConfirmed && txConfirmed === this.txid) {
|
||||||
|
this.stateService.markBlock$.next({ blockHeight: block.height });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCoins(): void {
|
||||||
|
this.error = null;
|
||||||
|
this.txid = '';
|
||||||
|
this.stateService.markBlock$.next({});
|
||||||
|
this.servicesApiService.requestTestnet4Coins$(this.faucetForm.get('address')?.value, parseInt(this.faucetForm.get('satoshis')?.value))
|
||||||
|
.subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
this.txid = response.txid;
|
||||||
|
this.websocketService.startTrackTransaction(this.txid);
|
||||||
|
this.audioService.playSound('cha-ching');
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}),
|
||||||
|
error: (response: HttpErrorResponse) => {
|
||||||
|
this.error = response.error;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setAmount(value: number): void {
|
||||||
|
if (this.faucetForm) {
|
||||||
|
this.faucetForm.get('satoshis').setValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get amount() { return this.faucetForm.get('satoshis')!; }
|
||||||
|
get invalidAmount() {
|
||||||
|
const amount = this.faucetForm.get('satoshis')!;
|
||||||
|
return amount?.invalid && (amount.dirty || amount.touched)
|
||||||
|
}
|
||||||
|
|
||||||
|
get address() { return this.faucetForm.get('address')!; }
|
||||||
|
get invalidAddress() {
|
||||||
|
const address = this.faucetForm.get('address')!;
|
||||||
|
return address?.invalid && (address.dirty || address.touched)
|
||||||
|
}
|
||||||
|
}
|
@ -79,7 +79,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
transition: background-color 1s;
|
transition: background-color 1s;
|
||||||
color: var(--color-fg);
|
color: #fff;
|
||||||
&.priority {
|
&.priority {
|
||||||
@media (767px < width < 992px), (width < 576px) {
|
@media (767px < width < 992px), (width < 576px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -16,8 +16,8 @@ export class FeesBoxComponent implements OnInit, OnDestroy {
|
|||||||
isLoading$: Observable<boolean>;
|
isLoading$: Observable<boolean>;
|
||||||
recommendedFees$: Observable<Recommendedfees>;
|
recommendedFees$: Observable<Recommendedfees>;
|
||||||
themeSubscription: Subscription;
|
themeSubscription: Subscription;
|
||||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
gradient = 'linear-gradient(to right, var(--skeleton-bg), var(--skeleton-bg))';
|
||||||
noPriority = '#2e324e';
|
noPriority = 'var(--skeleton-bg)';
|
||||||
fees: Recommendedfees;
|
fees: Recommendedfees;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<div *ngIf="stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING" class="mb-3 d-flex menu">
|
<div *ngIf="stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING || stateService.env.ACCELERATOR" class="mb-3 d-flex menu" [style]="{'flex-wrap': flexWrap ? 'wrap' : ''}">
|
||||||
|
|
||||||
<a routerLinkActive="active" class="btn btn-primary" [class]="padding"
|
<a routerLinkActive="active" class="btn btn-primary w-33"
|
||||||
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
|
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
|
||||||
|
|
||||||
<div ngbDropdown [class]="padding" *ngIf="stateService.env.MINING_DASHBOARD">
|
<div ngbDropdown class="w-33" *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="mining">Mining</button>
|
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="mining">Mining</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||||
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/pools' | relativeUrl]"
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/pools' | relativeUrl]"
|
||||||
@ -17,6 +17,8 @@
|
|||||||
i18n="mining.block-fee-rates">Block Fee Rates</a>
|
i18n="mining.block-fee-rates">Block Fee Rates</a>
|
||||||
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"
|
||||||
i18n="mining.block-fees">Block Fees</a>
|
i18n="mining.block-fees">Block Fees</a>
|
||||||
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-fees-subsidy' | relativeUrl]"
|
||||||
|
i18n="mining.block-fees">Block Fees Vs Subsidy</a>
|
||||||
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"
|
||||||
i18n="mining.block-rewards">Block Rewards</a>
|
i18n="mining.block-rewards">Block Rewards</a>
|
||||||
<a class="dropdown-item" routerLinkActive="active"
|
<a class="dropdown-item" routerLinkActive="active"
|
||||||
@ -26,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ngbDropdown [class]="padding" *ngIf="stateService.env.LIGHTNING">
|
<div ngbDropdown class="w-33" *ngIf="stateService.networkSupportsLightning()">
|
||||||
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="lightning">Lightning</button>
|
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="lightning">Lightning</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||||
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"
|
||||||
@ -43,6 +45,14 @@
|
|||||||
i18n="lightning.nodes-channels-world-map">Lightning Nodes Channels World Map</a>
|
i18n="lightning.nodes-channels-world-map">Lightning Nodes Channels World Map</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ngbDropdown class="w-33" *ngIf="stateService.env.ACCELERATOR">
|
||||||
|
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="accelerator.accelerations">Accelerations</button>
|
||||||
|
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||||
|
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]"
|
||||||
|
i18n="accelerator.acceleration-fees">Acceleration Fees</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 35px;
|
padding: 0 35px;
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
max-width: 400px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
@ -11,5 +11,6 @@
|
|||||||
&.last-child {
|
&.last-child {
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ import { WebsocketService } from '../../services/websocket.service';
|
|||||||
styleUrls: ['./graphs.component.scss'],
|
styleUrls: ['./graphs.component.scss'],
|
||||||
})
|
})
|
||||||
export class GraphsComponent implements OnInit {
|
export class GraphsComponent implements OnInit {
|
||||||
padding = 'w-50';
|
flexWrap = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
@ -18,8 +18,8 @@ export class GraphsComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.websocketService.want(['blocks']);
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
if (this.stateService.env.MINING_DASHBOARD === true && this.stateService.env.LIGHTNING === true) {
|
if (this.stateService.env.ACCELERATOR === true && (this.stateService.env.MINING_DASHBOARD === true || this.stateService.env.LIGHTNING === true)) {
|
||||||
this.padding = 'w-33';
|
this.flexWrap = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -66,7 +66,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
this.windowPreference = (this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference')) || '2h';
|
||||||
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
|
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
|
||||||
this.MA = this.calculateMA(this.data.series[0], windowSize);
|
this.MA = this.calculateMA(this.data.series[0], windowSize);
|
||||||
if (this.outlierCappingEnabled === true) {
|
if (this.outlierCappingEnabled === true) {
|
||||||
@ -216,22 +216,19 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||||||
type: 'line',
|
type: 'line',
|
||||||
},
|
},
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
const bestItem = params.reduce((best, item) => {
|
||||||
|
return (item.seriesName === 'data' && (!best || best.value[1] < item.value[1])) ? item : best;
|
||||||
|
}, null);
|
||||||
|
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, bestItem.axisValue);
|
||||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
|
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
|
||||||
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
|
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
|
||||||
params.map((item: any, index: number) => {
|
if (bestItem) {
|
||||||
|
|
||||||
//Do no include MA in tooltip legend!
|
|
||||||
if (item.seriesName !== 'MA') {
|
|
||||||
if (index < 26) {
|
|
||||||
itemFormatted += `<div class="item">
|
itemFormatted += `<div class="item">
|
||||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
<div class="indicator-container">${colorSpan(bestItem.color)}</div>
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<div class="value">${formatNumber(item.value[1], this.locale, '1.0-0')}<span class="symbol">vB/s</span></div>
|
<div class="value">${formatNumber(bestItem.value[1], this.locale, '1.0-0')}<span class="symbol">vB/s</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}"
|
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}"
|
||||||
style="width: ${(this.windowPreference === '2h' || this.template === 'widget') ? '125px' : '215px'}">${itemFormatted}</div>`;
|
style="width: ${(this.windowPreference === '2h' || this.template === 'widget') ? '125px' : '215px'}">${itemFormatted}</div>`;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item.active {
|
li.nav-item.active {
|
||||||
@ -17,7 +18,7 @@ fa-icon {
|
|||||||
.navbar {
|
.navbar {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
background-color: var(--bg);
|
background-color: var(--nav-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item {
|
li.nav-item {
|
||||||
@ -48,7 +49,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: var(--navbar-bg);
|
background: var(--nav-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<img [src]="enterpriseInfo.img" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
}
|
}
|
||||||
@if (enterpriseInfo?.header_img) {
|
@if (enterpriseInfo?.header_img) {
|
||||||
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="60px" class="mr-3">
|
||||||
} @else {
|
} @else {
|
||||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo" style="width: 200px; height: 50px"></app-svg-images>
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo" style="width: 200px; height: 50px"></app-svg-images>
|
||||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 200px; height: 50px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 200px; height: 50px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
padding-top: 80px;
|
padding-top: 80px;
|
||||||
|
background: var(--nav-bg);
|
||||||
|
|
||||||
header {
|
header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -18,7 +19,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--stat-box-bg);
|
background: var(--nav-bg);
|
||||||
text-align: start;
|
text-align: start;
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
<!-- Large screen -->
|
<!-- Large screen -->
|
||||||
<a class="navbar-brand d-none d-md-flex" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
<a class="navbar-brand d-none d-md-flex" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||||
|
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
||||||
|
@if (enterpriseInfo?.header_img) {
|
||||||
|
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="48px" class="mr-3">
|
||||||
|
} @else {
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
<div class="subdomain_container">
|
<div class="subdomain_container">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
</div>
|
</div>
|
||||||
<div class="vertical-line"></div>
|
<div class="vertical-line"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
|
||||||
@if (enterpriseInfo?.header_img) {
|
|
||||||
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
|
||||||
} @else {
|
|
||||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
||||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
||||||
}
|
}
|
||||||
@ -38,6 +38,9 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Mobile -->
|
<!-- Mobile -->
|
||||||
<a class="navbar-brand d-flex d-md-none justify-content-center" [ngClass]="{'dual-logos': subdomain, 'mr-0': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
<a class="navbar-brand d-flex d-md-none justify-content-center" [ngClass]="{'dual-logos': subdomain, 'mr-0': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||||
|
@if (enterpriseInfo?.header_img) {
|
||||||
|
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="42px">
|
||||||
|
} @else {
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
<div class="subdomain_container">
|
<div class="subdomain_container">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
@ -56,6 +59,7 @@
|
|||||||
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
|
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||||
@ -66,7 +70,7 @@
|
|||||||
<a ngbDropdownItem class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
<a ngbDropdownItem class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
||||||
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
||||||
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
|
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
|
||||||
<a ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet" [class.active]="network.val === 'testnet4'" [routerLink]="networkPaths['testnet4'] || '/testnet4'"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4 <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></a>
|
<a ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet4" [class.active]="network.val === 'testnet4'" [routerLink]="networkPaths['testnet4'] || '/testnet4'"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4 <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></a>
|
||||||
<h6 *ngIf="env.LIQUID_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
<h6 *ngIf="env.LIQUID_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
||||||
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquid'] || '')" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquid'] || '')" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
||||||
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquidtestnet'] || '/testnet')" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquid'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquidtestnet'] || '/testnet')" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquid'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
||||||
@ -88,7 +92,7 @@
|
|||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-lightning" *ngIf="stateService.env.LIGHTNING && lightningNetworks.includes(stateService.network)">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-lightning" *ngIf="stateService.networkSupportsLightning()">
|
||||||
<a class="nav-link" [routerLink]="['/lightning' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" i18n-title="master-page.lightning" title="Lightning Explorer"></fa-icon>
|
<a class="nav-link" [routerLink]="['/lightning' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" i18n-title="master-page.lightning" title="Lightning Explorer"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -98,6 +102,9 @@
|
|||||||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
||||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" routerLinkActive="active" id="btn-faucet" *ngIf="stateService.isMempoolSpaceBuild && stateService.env.OFFICIAL_MEMPOOL_SPACE && stateService.network === 'testnet4'">
|
||||||
|
<a class="nav-link" [routerLink]="['/faucet' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'faucet-drip']" [fixedWidth]="true" i18n-title="master-page.faucet" title="Faucet"></fa-icon></a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" id="btn-docs">
|
<li class="nav-item" routerLinkActive="active" id="btn-docs">
|
||||||
<a class="nav-link" [routerLink]="['/docs' | relativeUrl ]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/docs' | relativeUrl ]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ fa-icon {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--bg);
|
background-color: var(--nav-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item {
|
li.nav-item {
|
||||||
@ -59,7 +60,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: var(--navbar-bg);
|
background: var(--nav-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -27,7 +27,6 @@ export class MasterPageComponent implements OnInit, OnDestroy {
|
|||||||
subdomain = '';
|
subdomain = '';
|
||||||
networkPaths: { [network: string]: string };
|
networkPaths: { [network: string]: string };
|
||||||
networkPaths$: Observable<Record<string, string>>;
|
networkPaths$: Observable<Record<string, string>>;
|
||||||
lightningNetworks = ['', 'mainnet', 'bitcoin', 'testnet', 'signet'];
|
|
||||||
footerVisible = true;
|
footerVisible = true;
|
||||||
user: any = undefined;
|
user: any = undefined;
|
||||||
servicesEnabled = false;
|
servicesEnabled = false;
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
.fee-span {
|
.fee-span {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: #fff000;
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-count {
|
.transaction-count {
|
||||||
@ -119,7 +119,7 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-bottom: calc(var(--block-size) * 0.3) solid #FFF;
|
border-bottom: calc(var(--block-size) * 0.3) solid var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockLink {
|
.blockLink {
|
||||||
|
@ -77,7 +77,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
this.isWidget = this.template === 'widget';
|
this.isWidget = this.template === 'widget';
|
||||||
this.showCount = !this.isWidget && !this.hideCount;
|
this.showCount = !this.isWidget && !this.hideCount;
|
||||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
this.windowPreference = (this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference')) || '2h';
|
||||||
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
||||||
this.mountFeeChart();
|
this.mountFeeChart();
|
||||||
}
|
}
|
||||||
@ -256,11 +256,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const itemFormatted = [];
|
const itemFormatted = [];
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
let progressPercentageText = '';
|
let progressPercentageText = '';
|
||||||
let countItem;
|
const unfilteredItems = this.inverted ? [...params].reverse() : params;
|
||||||
let items = this.inverted ? [...params].reverse() : params;
|
const countItem = unfilteredItems.find(p => p.seriesName === 'count');
|
||||||
if (items[items.length - 1].seriesName === 'count') {
|
const usedSeries = {};
|
||||||
countItem = items.pop();
|
const items = unfilteredItems.filter(p => {
|
||||||
|
if (usedSeries[p.seriesName] || p.seriesName === 'count') {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
usedSeries[p.seriesName] = true;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
items.map((item: any, index: number) => {
|
items.map((item: any, index: number) => {
|
||||||
sum += item.value[1];
|
sum += item.value[1];
|
||||||
const progressPercentage = (item.value[1] / totalValue) * 100;
|
const progressPercentage = (item.value[1] / totalValue) * 100;
|
||||||
|
@ -63,7 +63,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
.rbf-tooltip {
|
.rbf-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -159,7 +159,7 @@
|
|||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
.shape-border {
|
.shape-border {
|
||||||
background: #9339f4;
|
background: var(--mainnet-alt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,7 @@
|
|||||||
width: calc(1em + 16px);
|
width: calc(1em + 16px);
|
||||||
|
|
||||||
.shape {
|
.shape {
|
||||||
border: solid 4px #9339f4;
|
border: solid 4px var(--mainnet-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -122,7 +122,7 @@ export class SearchFormComponent implements OnInit {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this.isTypeaheading$.next(true);
|
this.isTypeaheading$.next(true);
|
||||||
if (!this.stateService.env.LIGHTNING) {
|
if (!this.stateService.networkSupportsLightning()) {
|
||||||
return zip(
|
return zip(
|
||||||
this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
|
this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
|
||||||
[{ nodes: [], channels: [] }],
|
[{ nodes: [], channels: [] }],
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.formRadioGroup.mining {
|
.formRadioGroup.mining {
|
||||||
@media (min-width: 1035px) {
|
@media (min-width: 1200px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -100px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,9 @@ export class TelevisionComponent implements OnInit, OnDestroy {
|
|||||||
mempoolStats = newStats;
|
mempoolStats = newStats;
|
||||||
} else if (['2h', '24h'].includes(this.fragment)) {
|
} else if (['2h', '24h'].includes(this.fragment)) {
|
||||||
mempoolStats.unshift(newStats[0]);
|
mempoolStats.unshift(newStats[0]);
|
||||||
mempoolStats = mempoolStats.slice(0, mempoolStats.length - 1);
|
const now = Date.now() / 1000;
|
||||||
|
const start = now - (this.fragment === '2h' ? (2 * 60 * 60) : (24 * 60 * 60) );
|
||||||
|
mempoolStats = mempoolStats.filter(p => p.added >= start);
|
||||||
}
|
}
|
||||||
return mempoolStats;
|
return mempoolStats;
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
|
|
||||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||||
|
|
||||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { Subscription } from 'rxjs';
|
|||||||
})
|
})
|
||||||
export class ThemeSelectorComponent implements OnInit {
|
export class ThemeSelectorComponent implements OnInit {
|
||||||
themeForm: UntypedFormGroup;
|
themeForm: UntypedFormGroup;
|
||||||
themes = ['default', 'contrast', 'wiz'];
|
themes = ['default', 'contrast', 'wiz', 'bukele'];
|
||||||
themeSubscription: Subscription;
|
themeSubscription: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,9 +1,26 @@
|
|||||||
<div class="mobile-wrapper">
|
<div class="mobile-wrapper">
|
||||||
<div class="mobile-container">
|
<div class="mobile-container">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="field nav-header">
|
<div class="nav-header">
|
||||||
<app-svg-images name="officialMempoolSpace" style="width: 144px; height: 36px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
@if (enterpriseInfo?.header_img) {
|
||||||
<div [ngSwitch]="network" class="network-label">
|
<a class="d-flex" [routerLink]="['/' | relativeUrl]">
|
||||||
|
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="42px">
|
||||||
|
</a>
|
||||||
|
} @else if (enterpriseInfo?.img || enterpriseInfo?.imageMd5) {
|
||||||
|
<a [routerLink]="['/' | relativeUrl]">
|
||||||
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + enterpriseInfo.name + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
|
</a>
|
||||||
|
<div class="vertical-line"></div>
|
||||||
|
}
|
||||||
|
@if (!enterpriseInfo?.header_img) {
|
||||||
|
<a [routerLink]="['/' | relativeUrl]">
|
||||||
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" style="width: 144px; height: 36px" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo"></app-svg-images>
|
||||||
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 144px; height: 36px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (enterpriseInfo?.header_img || (!enterpriseInfo?.img && !enterpriseInfo?.imageMd5)) {
|
||||||
|
<div [ngSwitch]="network" class="network-label" [class.hide-name]="enterpriseInfo?.header_img">
|
||||||
<span *ngSwitchCase="'signet'" class="network signet"><span class="name">Bitcoin Signet</span><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'signet'" class="network signet"><span class="name">Bitcoin Signet</span><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchCase="'testnet'" class="network testnet"><span class="name">Bitcoin Testnet3</span><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'testnet'" class="network testnet"><span class="name">Bitcoin Testnet3</span><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchCase="'testnet4'" class="network testnet"><span class="name">Bitcoin Testnet4</span><app-svg-images name="testnet4" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'testnet4'" class="network testnet"><span class="name">Bitcoin Testnet4</span><app-svg-images name="testnet4" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
@ -11,6 +28,7 @@
|
|||||||
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><span class="name">Liquid Testnet</span><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><span class="name">Liquid Testnet</span><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchDefault class="network mainnet"><span class="name">Bitcoin</span><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchDefault class="network mainnet"><span class="name">Bitcoin</span><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="label" i18n="shared.transaction">Transaction</div>
|
<div class="label" i18n="shared.transaction">Transaction</div>
|
||||||
|
@ -40,7 +40,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-header {
|
.nav-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1em;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: var(--nav-bg);
|
||||||
box-shadow: 0 -5px 15px #000;
|
box-shadow: 0 -5px 15px #000;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -53,6 +60,40 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hide-name .name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subdomain_logo {
|
||||||
|
height: 35px;
|
||||||
|
overflow: clip;
|
||||||
|
max-width: 140px;
|
||||||
|
margin: auto;
|
||||||
|
align-self: center;
|
||||||
|
.rounded {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subdomain_container {
|
||||||
|
max-width: 140px;
|
||||||
|
text-align: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-line {
|
||||||
|
border-left: 1px solid #444;
|
||||||
|
height: 30px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-holder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +113,10 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
scrollIntoAccelPreview = false;
|
scrollIntoAccelPreview = false;
|
||||||
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
||||||
|
|
||||||
|
enterpriseInfo: any;
|
||||||
|
enterpriseInfo$: Subscription;
|
||||||
|
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
@ -152,6 +156,10 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.enterpriseService.page();
|
this.enterpriseService.page();
|
||||||
|
|
||||||
|
this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => {
|
||||||
|
this.enterpriseInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.stateService.networkChanged$.subscribe(
|
this.stateService.networkChanged$.subscribe(
|
||||||
(network) => {
|
(network) => {
|
||||||
@ -702,6 +710,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.miningSubscription?.unsubscribe();
|
this.miningSubscription?.unsubscribe();
|
||||||
this.currencyChangeSubscription?.unsubscribe();
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
|
this.enterpriseInfo$?.unsubscribe();
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,8 @@ td.amount.large {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
.assetBox {
|
.assetBox {
|
||||||
background-color: #653b9c90;
|
background: color-mix(in srgb, var(--tertiary) 56%, transparent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-container {
|
.details-container {
|
||||||
|
@ -112,7 +112,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
),
|
),
|
||||||
this.refreshChannels$
|
this.refreshChannels$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(() => this.stateService.env.LIGHTNING),
|
filter(() => this.stateService.networkSupportsLightning()),
|
||||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
// handle 404
|
// handle 404
|
||||||
@ -248,7 +248,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
if (txIds.length && !this.cached) {
|
if (txIds.length && !this.cached) {
|
||||||
this.refreshOutspends$.next(txIds);
|
this.refreshOutspends$.next(txIds);
|
||||||
}
|
}
|
||||||
if (this.stateService.env.LIGHTNING) {
|
if (this.stateService.networkSupportsLightning()) {
|
||||||
const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
|
const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
|
||||||
if (txIds.length) {
|
if (txIds.length) {
|
||||||
this.refreshChannels$.next(txIds);
|
this.refreshChannels$.next(txIds);
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
<a href="#" (click)="twitterLogin()"
|
||||||
|
[class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')"
|
||||||
|
style="background-color: #1DA1F2" [style]="width ? 'width: ' + width : ''">
|
||||||
|
<img src="./resources/twitter.svg" height="25" style="padding: 2px" [alt]="buttonString + ' with Twitter'" />
|
||||||
|
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
||||||
|
</a>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-twitter-login',
|
||||||
|
templateUrl: './twitter-login.component.html',
|
||||||
|
})
|
||||||
|
export class TwitterLogin {
|
||||||
|
@Input() width: string | null = null;
|
||||||
|
@Input() customClass: string | null = null;
|
||||||
|
@Input() buttonString: string= 'unset';
|
||||||
|
@Input() redirectTo: string | null = null;
|
||||||
|
@Output() clicked = new EventEmitter<boolean>();
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
twitterLogin() {
|
||||||
|
this.clicked.emit(true);
|
||||||
|
if (this.redirectTo) {
|
||||||
|
location.replace(`/api/v1/services/auth/login/twitter?redirectTo=${encodeURIComponent(this.redirectTo)}`);
|
||||||
|
} else {
|
||||||
|
location.replace(`/api/v1/services/auth/login/twitter?redirectTo=${location.href}`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
@if (loading) {
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
} @else if (error) {
|
||||||
|
<div class="error-wrapper">
|
||||||
|
<span>failed to load X timeline</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" allowfullscreen="true"
|
||||||
|
title="Twitter Timeline"
|
||||||
|
[src]="iframeSrc"
|
||||||
|
style="position: static; visibility: visible; width: 100%; height: 100%; display: block; flex-grow: 1;"
|
||||||
|
(load)="onReady()"
|
||||||
|
></iframe>
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
.spinner-wrapper, .error-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
import { Component, Input, ChangeDetectionStrategy, SecurityContext, SimpleChanges, OnChanges } from '@angular/core';
|
||||||
|
import { LanguageService } from '../../services/language.service';
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-twitter-widget',
|
||||||
|
templateUrl: './twitter-widget.component.html',
|
||||||
|
styleUrls: ['./twitter-widget.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class TwitterWidgetComponent implements OnChanges {
|
||||||
|
@Input() handle: string;
|
||||||
|
@Input() width = 300;
|
||||||
|
@Input() height = 400;
|
||||||
|
|
||||||
|
loading: boolean = true;
|
||||||
|
error: boolean = false;
|
||||||
|
lang: string = 'en';
|
||||||
|
|
||||||
|
iframeSrc: SafeResourceUrl;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private languageService: LanguageService,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
) {
|
||||||
|
this.lang = this.languageService.getLanguage();
|
||||||
|
this.setIframeSrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.handle) {
|
||||||
|
this.setIframeSrc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIframeSrc(): void {
|
||||||
|
if (this.handle) {
|
||||||
|
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL,
|
||||||
|
`https://syndication.twitter.com/srv/timeline-profile/screen-name/${this.handle}?creatorScreenName=mempool`
|
||||||
|
+ '&dnt=true'
|
||||||
|
+ '&embedId=twitter-widget-0'
|
||||||
|
+ '&features=eyJ0ZndfdGltZWxpbmVfgbGlzdCI6eyJidWNrZXQiOltdLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X2ZvbGxvd2VyX2NvdW50X3N1bnNldCI6eyJidWNrZXQiOnRydWUsInZlcnNpb24iOm51bGx9LCJ0ZndfdHdlZXRfZWRpdF9iYWNrZW5kIjp7ImJ1Y2tldCI6Im9uIiwidmVyc2lvbiI6bnVsbH0sInRmd19yZWZzcmNfc2Vzc2lvbiI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfZm9zbnJfc29mdF9pbnRlcnZlbnRpb25zX2VuYWJsZWQiOnsiYnVja2V0Ijoib24iLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X21peGVkX21lZGlhXzE1ODk3Ijp7ImJ1Y2tldCI6InRyZWF0bWVudCIsInZlcnNpb24iOm51bGx9LCJ0ZndfZXhwZXJpbWVudHNfY29va2llX2V4cGlyYXRpb24iOnsiYnVja2V0IjoxMjA5NjAwLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X3Nob3dfYmlyZHdhdGNoX3Bpdm90c19lbmFibGVkIjp7ImJ1Y2tldCI6Im9uIiwidmVyc2lvbiI6bnVsbH0sInRmd19kdXBsaWNhdGVfc2NyaWJlc190b19zZXR0aW5ncyI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfdXNlX3Byb2ZpbGVfaW1hZ2Vfc2hhcGVfZW5hYmxlZCI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfdmlkZW9faGxzX2R5bmFtaWNfbWFuaWZlc3RzXzE1MDgyIjp7ImJ1Y2tldCI6InRydWVfYml0cmF0ZSIsInZlcnNpb24iOm51bGx9LCJ0ZndfbGVnYWN5X3RpbWVsaW5lX3N1bnNldCI6eyJidWNrZXQiOnRydWUsInZlcnNpb24iOm51bGx9LCJ0ZndfdHdlZXRfZWRpdF9mcm9udGVuZCI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9fQ%3D%3D'
|
||||||
|
+ '&frame=false'
|
||||||
|
+ '&hideBorder=true'
|
||||||
|
+ '&hideFooter=false'
|
||||||
|
+ '&hideHeader=true'
|
||||||
|
+ '&hideScrollBar=false'
|
||||||
|
+ `&lang=${this.lang}`
|
||||||
|
+ '&maxHeight=500px'
|
||||||
|
+ '&origin=https%3A%2F%2Fmempool.space%2F'
|
||||||
|
// + '&sessionId=88f6d661d0dcca99c43c0a590f6a3e61c89226a9'
|
||||||
|
+ '&showHeader=false'
|
||||||
|
+ '&showReplies=false'
|
||||||
|
+ '&siteScreenName=mempool'
|
||||||
|
+ '&theme=dark'
|
||||||
|
+ '&transparent=true'
|
||||||
|
+ '&widgetsVersion=2615f7e52b7e0%3A1702314776716'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady(): void {
|
||||||
|
this.loading = false;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFailed(): void {
|
||||||
|
this.loading = false;
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
.bowtie-graph-tooltip {
|
.bowtie-graph-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: var(--tooltip-grey);
|
color: var(--tooltip-grey);
|
||||||
|
@ -84,7 +84,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0', '#9339f400'],
|
'': ['var(--mainnet-alt)', 'var(--primary)', 'color-mix(in srgb, var(--mainnet-alt) 1%, transparent)'],
|
||||||
// liquid: ['#116761', '#183550'],
|
// liquid: ['#116761', '#183550'],
|
||||||
liquid: ['#09a197', '#0f62af', '#09a19700'],
|
liquid: ['#09a197', '#0f62af', '#09a19700'],
|
||||||
// 'liquidtestnet': ['#494a4a', '#272e46'],
|
// 'liquidtestnet': ['#494a4a', '#272e46'],
|
||||||
@ -96,7 +96,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
signet: ['#d24fc8', '#a84fd2', '#d24fc800'],
|
signet: ['#d24fc8', '#a84fd2', '#d24fc800'],
|
||||||
};
|
};
|
||||||
|
|
||||||
gradient: string[] = ['#105fb0', '#105fb0'];
|
gradient: string[] = ['var(--primary)', 'var(--primary)'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
@ -301,7 +301,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -435,7 +436,8 @@
|
|||||||
|
|
||||||
.in-progress-message {
|
.in-progress-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user