Compare commits
61 Commits
v2.5.0-dev
...
mononaut/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420cac42aa | ||
|
|
1b68f32adc | ||
|
|
f2f6e3769a | ||
|
|
f9f8bd25f8 | ||
|
|
26a92cda45 | ||
|
|
a51b4e88d8 | ||
|
|
a975936d3c | ||
|
|
fbbd86d8e0 | ||
|
|
8ccfa5b038 | ||
|
|
04006b8c98 | ||
|
|
3317f5e6db | ||
|
|
79e1beb2ca | ||
|
|
811d0c824f | ||
|
|
f3adab7d26 | ||
|
|
d730366ea7 | ||
|
|
7e60526887 | ||
|
|
ab69c03d8d | ||
|
|
efa352821a | ||
|
|
1dcf6ab599 | ||
|
|
869d7df844 | ||
|
|
597536efaf | ||
|
|
1f5754943a | ||
|
|
e98d03431c | ||
|
|
39d92f57fa | ||
|
|
313df79e33 | ||
|
|
13ceb368e2 | ||
|
|
72e6e36cbb | ||
|
|
729f2aff3e | ||
|
|
1348e2318d | ||
|
|
731443f670 | ||
|
|
fc2edbf1d1 | ||
|
|
8ac514733a | ||
|
|
e986aaf1d9 | ||
|
|
606e6df834 | ||
|
|
743f2a1cd4 | ||
|
|
5e633344c5 | ||
|
|
c4d5ea971e | ||
|
|
04fec6c894 | ||
|
|
343a48818b | ||
|
|
028a26f574 | ||
|
|
dcd0a53fba | ||
|
|
ea5ec7bc32 | ||
|
|
1513c61cd5 | ||
|
|
f18ea6a7a3 | ||
|
|
35ee58befb | ||
|
|
79f79b0e3b | ||
|
|
250df0d56c | ||
|
|
fb137e6247 | ||
|
|
1a4f699c95 | ||
|
|
56b6f79f97 | ||
|
|
4d0637768d | ||
|
|
28c21b3770 | ||
|
|
5658e053d0 | ||
|
|
d17ccbc5ae | ||
|
|
ade3c09b2a | ||
|
|
3572ba837d | ||
|
|
33775f32e2 | ||
|
|
5d95eb475e | ||
|
|
c57542c8ae | ||
|
|
dd5a1847d0 | ||
|
|
a28544d046 |
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -6,23 +6,27 @@ updates:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- package-ecosystem: npm
|
||||
directory: "/frontend"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- package-ecosystem: docker
|
||||
directory: "/docker/backend"
|
||||
schedule:
|
||||
interval: daily
|
||||
ignore:
|
||||
- update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
ignore:
|
||||
- update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
18
.github/workflows/cypress.yml
vendored
18
.github/workflows/cypress.yml
vendored
@@ -2,7 +2,7 @@ name: Cypress Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, review_requested, synchronize ]
|
||||
types: [opened, review_requested, synchronize]
|
||||
jobs:
|
||||
cypress:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
@@ -24,36 +24,36 @@ jobs:
|
||||
- module: "bisq"
|
||||
spec: |
|
||||
cypress/e2e/bisq/bisq.spec.ts
|
||||
|
||||
|
||||
name: E2E tests for ${{ matrix.module }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.module }}
|
||||
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.15.0
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
uses: cypress-io/github-action@v4
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
wait-on: 'http://localhost:4200'
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: ${{ matrix.spec }}
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
@@ -742,7 +742,7 @@ class Blocks {
|
||||
|
||||
public async $indexCPFP(hash: string, height: number): Promise<void> {
|
||||
let transactions;
|
||||
if (false/*Common.blocksSummariesIndexingEnabled()*/) {
|
||||
if (Common.blocksSummariesIndexingEnabled()) {
|
||||
transactions = await this.$getStrippedBlockTransactions(hash);
|
||||
const rawBlock = await bitcoinApi.$getRawBlock(hash);
|
||||
const block = Block.fromBuffer(rawBlock);
|
||||
@@ -751,10 +751,11 @@ class Blocks {
|
||||
txMap[tx.getId()] = tx;
|
||||
}
|
||||
for (const tx of transactions) {
|
||||
// convert from bitcoinjs to esplora vin format
|
||||
if (txMap[tx.txid]?.ins) {
|
||||
tx.vin = txMap[tx.txid].ins.map(vin => {
|
||||
return {
|
||||
txid: vin.hash
|
||||
txid: vin.hash.slice().reverse().toString('hex')
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -763,6 +764,7 @@ class Blocks {
|
||||
const block = await bitcoinClient.getBlock(hash, 2);
|
||||
transactions = block.tx.map(tx => {
|
||||
tx.vsize = tx.weight / 4;
|
||||
tx.fee *= 100_000_000;
|
||||
return tx;
|
||||
});
|
||||
}
|
||||
@@ -778,9 +780,9 @@ class Blocks {
|
||||
totalFee += tx?.fee || 0;
|
||||
totalVSize += tx.vsize;
|
||||
});
|
||||
const effectiveFeePerVsize = (totalFee * 100_000_000) / totalVSize;
|
||||
const effectiveFeePerVsize = totalFee / totalVSize;
|
||||
if (cluster.length > 1) {
|
||||
await cpfpRepository.$saveCluster(height, cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: (tx.fee || 0) * 100_000_000 }; }), effectiveFeePerVsize);
|
||||
await cpfpRepository.$saveCluster(height, cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: tx.fee || 0 }; }), effectiveFeePerVsize);
|
||||
for (const tx of cluster) {
|
||||
await transactionRepository.$setCluster(tx.txid, cluster[0].txid);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import logger from '../logger';
|
||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta } from '../mempool.interfaces';
|
||||
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import config from '../config';
|
||||
import { StaticPool } from 'node-worker-threads-pool';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
|
||||
class MempoolBlocks {
|
||||
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
||||
private makeTemplatesPool = new StaticPool({
|
||||
size: 1,
|
||||
task: path.resolve(__dirname, './tx-selection-worker.js'),
|
||||
});
|
||||
private txSelectionWorker: Worker | null = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
@@ -146,27 +143,159 @@ class MempoolBlocks {
|
||||
return mempoolBlockDeltas;
|
||||
}
|
||||
|
||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null = null, condenseRest = false): Promise<void> {
|
||||
const { mempool, blocks } = await this.makeTemplatesPool.exec({ mempool: newMempool, blockLimit, weightLimit, condenseRest });
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks);
|
||||
|
||||
// copy CPFP info across to main thread's mempool
|
||||
Object.keys(newMempool).forEach((txid) => {
|
||||
if (newMempool[txid] && mempool[txid]) {
|
||||
newMempool[txid].effectiveFeePerVsize = mempool[txid].effectiveFeePerVsize;
|
||||
newMempool[txid].ancestors = mempool[txid].ancestors;
|
||||
newMempool[txid].descendants = mempool[txid].descendants;
|
||||
newMempool[txid].bestDescendant = mempool[txid].bestDescendant;
|
||||
newMempool[txid].cpfpChecked = mempool[txid].cpfpChecked;
|
||||
}
|
||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }): Promise<void> {
|
||||
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||
// to reduce the overhead of passing this data to the worker thread
|
||||
const strippedMempool: { [txid: string]: ThreadTransaction } = {};
|
||||
Object.values(newMempool).forEach(entry => {
|
||||
strippedMempool[entry.txid] = {
|
||||
txid: entry.txid,
|
||||
fee: entry.fee,
|
||||
weight: entry.weight,
|
||||
feePerVsize: entry.fee / (entry.weight / 4),
|
||||
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||
vin: entry.vin.map(v => v.txid),
|
||||
};
|
||||
});
|
||||
|
||||
this.mempoolBlocks = blocks;
|
||||
// (re)initialize tx selection worker thread
|
||||
if (!this.txSelectionWorker) {
|
||||
this.txSelectionWorker = new Worker(path.resolve(__dirname, './tx-selection-worker.js'));
|
||||
// if the thread throws an unexpected error, or exits for any other reason,
|
||||
// reset worker state so that it will be re-initialized on the next run
|
||||
this.txSelectionWorker.once('error', () => {
|
||||
this.txSelectionWorker = null;
|
||||
});
|
||||
this.txSelectionWorker.once('exit', () => {
|
||||
this.txSelectionWorker = null;
|
||||
});
|
||||
}
|
||||
|
||||
// run the block construction algorithm in a separate thread, and wait for a result
|
||||
let threadErrorListener;
|
||||
try {
|
||||
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||
threadErrorListener = reject;
|
||||
this.txSelectionWorker?.once('message', (result): void => {
|
||||
resolve(result);
|
||||
});
|
||||
this.txSelectionWorker?.once('error', reject);
|
||||
});
|
||||
this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
|
||||
const { blocks, clusters } = await workerResultPromise;
|
||||
|
||||
this.processBlockTemplates(newMempool, blocks, clusters);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
} catch (e) {
|
||||
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[]): Promise<void> {
|
||||
if (!this.txSelectionWorker) {
|
||||
// need to reset the worker
|
||||
return this.makeBlockTemplates(newMempool);
|
||||
}
|
||||
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||
// to reduce the overhead of passing this data to the worker thread
|
||||
const addedStripped: ThreadTransaction[] = added.map(entry => {
|
||||
return {
|
||||
txid: entry.txid,
|
||||
fee: entry.fee,
|
||||
weight: entry.weight,
|
||||
feePerVsize: entry.fee / (entry.weight / 4),
|
||||
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||
vin: entry.vin.map(v => v.txid),
|
||||
};
|
||||
});
|
||||
|
||||
// run the block construction algorithm in a separate thread, and wait for a result
|
||||
let threadErrorListener;
|
||||
try {
|
||||
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||
threadErrorListener = reject;
|
||||
this.txSelectionWorker?.once('message', (result): void => {
|
||||
resolve(result);
|
||||
});
|
||||
this.txSelectionWorker?.once('error', reject);
|
||||
});
|
||||
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
|
||||
const { blocks, clusters } = await workerResultPromise;
|
||||
|
||||
this.processBlockTemplates(newMempool, blocks, clusters);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
} catch (e) {
|
||||
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private processBlockTemplates(mempool, blocks, clusters): void {
|
||||
// update this thread's mempool with the results
|
||||
blocks.forEach(block => {
|
||||
block.forEach(tx => {
|
||||
if (tx.txid in mempool) {
|
||||
if (tx.effectiveFeePerVsize != null) {
|
||||
mempool[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize;
|
||||
}
|
||||
if (tx.cpfpRoot && tx.cpfpRoot in clusters) {
|
||||
const ancestors: Ancestor[] = [];
|
||||
const descendants: Ancestor[] = [];
|
||||
const cluster = clusters[tx.cpfpRoot];
|
||||
let matched = false;
|
||||
cluster.forEach(txid => {
|
||||
if (txid === tx.txid) {
|
||||
matched = true;
|
||||
} else {
|
||||
const relative = {
|
||||
txid: txid,
|
||||
fee: mempool[txid].fee,
|
||||
weight: mempool[txid].weight,
|
||||
};
|
||||
if (matched) {
|
||||
descendants.push(relative);
|
||||
} else {
|
||||
ancestors.push(relative);
|
||||
}
|
||||
}
|
||||
});
|
||||
mempool[tx.txid].ancestors = ancestors;
|
||||
mempool[tx.txid].descendants = descendants;
|
||||
mempool[tx.txid].bestDescendant = null;
|
||||
}
|
||||
mempool[tx.txid].cpfpChecked = tx.cpfpChecked;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// unpack the condensed blocks into proper mempool blocks
|
||||
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
|
||||
return this.dataToMempoolBlocks(transactions.map(tx => {
|
||||
return mempool[tx.txid] || null;
|
||||
}).filter(tx => !!tx), undefined, undefined, blockIndex);
|
||||
});
|
||||
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||
|
||||
this.mempoolBlocks = mempoolBlocks;
|
||||
this.mempoolBlockDeltas = deltas;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
blockSize: number | undefined, blockWeight: number | undefined, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
let totalSize = blockSize || 0;
|
||||
let totalWeight = blockWeight || 0;
|
||||
if (blockSize === undefined && blockWeight === undefined) {
|
||||
totalSize = 0;
|
||||
totalWeight = 0;
|
||||
transactions.forEach(tx => {
|
||||
totalSize += tx.size;
|
||||
totalWeight += tx.weight;
|
||||
});
|
||||
}
|
||||
let rangeLength = 4;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
@@ -177,8 +306,8 @@ class MempoolBlocks {
|
||||
rangeLength = 8;
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockWeight / 4,
|
||||
blockSize: totalSize,
|
||||
blockVSize: totalWeight / 4,
|
||||
nTx: transactions.length,
|
||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
|
||||
@@ -21,7 +21,7 @@ class Mempool {
|
||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
||||
|
||||
private txPerSecondArray: number[] = [];
|
||||
private txPerSecond: number = 0;
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import config from '../config';
|
||||
import logger from '../logger';
|
||||
import { TransactionExtended, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||
import { ThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||
import { PairingHeap } from '../utils/pairing-heap';
|
||||
import { Common } from './common';
|
||||
import { parentPort } from 'worker_threads';
|
||||
|
||||
let mempool: { [txid: string]: ThreadTransaction } = {};
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', (params: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null, condenseRest: boolean}) => {
|
||||
const { mempool, blocks } = makeBlockTemplates(params);
|
||||
parentPort.on('message', (params) => {
|
||||
if (params.type === 'set') {
|
||||
mempool = params.mempool;
|
||||
} else if (params.type === 'update') {
|
||||
params.added.forEach(tx => {
|
||||
mempool[tx.txid] = tx;
|
||||
});
|
||||
params.removed.forEach(txid => {
|
||||
delete mempool[txid];
|
||||
});
|
||||
}
|
||||
|
||||
const { blocks, clusters } = makeBlockTemplates(mempool);
|
||||
|
||||
// return the result to main thread.
|
||||
if (parentPort) {
|
||||
parentPort.postMessage({ mempool, blocks });
|
||||
parentPort.postMessage({ blocks, clusters });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -19,35 +32,24 @@ if (parentPort) {
|
||||
/*
|
||||
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
|
||||
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
||||
*
|
||||
* blockLimit: number of blocks to build in total.
|
||||
* weightLimit: maximum weight of transactions to consider using the selection algorithm.
|
||||
* if weightLimit is significantly lower than the mempool size, results may start to diverge from getBlockTemplate
|
||||
* condenseRest: whether to ignore excess transactions or append them to the final block.
|
||||
*/
|
||||
function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit?: number | null, condenseRest?: boolean | null })
|
||||
: { mempool: { [txid: string]: TransactionExtended }, blocks: MempoolBlockWithTransactions[] } {
|
||||
function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
: { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } } {
|
||||
const start = Date.now();
|
||||
const auditPool: { [txid: string]: AuditTransaction } = {};
|
||||
const mempoolArray: AuditTransaction[] = [];
|
||||
const restOfArray: TransactionExtended[] = [];
|
||||
const restOfArray: ThreadTransaction[] = [];
|
||||
const cpfpClusters: { [root: string]: string[] } = {};
|
||||
|
||||
let weight = 0;
|
||||
const maxWeight = weightLimit ? Math.max(4_000_000 * blockLimit, weightLimit) : Infinity;
|
||||
// grab the top feerate txs up to maxWeight
|
||||
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
||||
weight += tx.weight;
|
||||
if (weight >= maxWeight) {
|
||||
restOfArray.push(tx);
|
||||
return;
|
||||
}
|
||||
// initializing everything up front helps V8 optimize property access later
|
||||
auditPool[tx.txid] = {
|
||||
txid: tx.txid,
|
||||
fee: tx.fee,
|
||||
size: tx.size,
|
||||
weight: tx.weight,
|
||||
feePerVsize: tx.feePerVsize,
|
||||
effectiveFeePerVsize: tx.feePerVsize,
|
||||
vin: tx.vin,
|
||||
relativesSet: false,
|
||||
ancestorMap: new Map<string, AuditTransaction>(),
|
||||
@@ -74,7 +76,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
|
||||
// Build blocks by greedily choosing the highest feerate package
|
||||
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||
const blocks: MempoolBlockWithTransactions[] = [];
|
||||
const blocks: ThreadTransaction[][] = [];
|
||||
let blockWeight = 4000;
|
||||
let blockSize = 0;
|
||||
let transactions: AuditTransaction[] = [];
|
||||
@@ -82,7 +84,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
let overflow: AuditTransaction[] = [];
|
||||
let failures = 0;
|
||||
let top = 0;
|
||||
while ((top < mempoolArray.length || !modified.isEmpty()) && (condenseRest || blocks.length < blockLimit)) {
|
||||
while ((top < mempoolArray.length || !modified.isEmpty())) {
|
||||
// skip invalid transactions
|
||||
while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) {
|
||||
top++;
|
||||
@@ -107,9 +109,13 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
// Check if the package fits into this block
|
||||
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
||||
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||
const descendants: AuditTransaction[] = [];
|
||||
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
||||
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
||||
let isCluster = false;
|
||||
if (sortedTxSet.length > 1) {
|
||||
cpfpClusters[nextTx.txid] = sortedTxSet.map(tx => tx.txid);
|
||||
isCluster = true;
|
||||
}
|
||||
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
||||
const used: AuditTransaction[] = [];
|
||||
while (sortedTxSet.length) {
|
||||
@@ -119,21 +125,9 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
ancestor.usedBy = nextTx.txid;
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
||||
mempoolTx.ancestors = sortedTxSet.map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
}).reverse();
|
||||
mempoolTx.descendants = descendants.map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
});
|
||||
descendants.push(ancestor);
|
||||
if (isCluster) {
|
||||
mempoolTx.cpfpRoot = nextTx.txid;
|
||||
}
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(ancestor);
|
||||
blockSize += ancestor.size;
|
||||
@@ -159,10 +153,10 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
// this block is full
|
||||
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
||||
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
||||
if ((exceededPackageTries || queueEmpty) && (!condenseRest || blocks.length < blockLimit - 1)) {
|
||||
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
||||
// construct this block
|
||||
if (transactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
||||
blocks.push(transactions.map(t => mempool[t.txid]));
|
||||
}
|
||||
// reset for the next block
|
||||
transactions = [];
|
||||
@@ -181,55 +175,40 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
||||
overflow = [];
|
||||
}
|
||||
}
|
||||
if (condenseRest) {
|
||||
// pack any leftover transactions into the last block
|
||||
for (const tx of overflow) {
|
||||
if (!tx || tx?.used) {
|
||||
continue;
|
||||
}
|
||||
blockWeight += tx.weight;
|
||||
blockSize += tx.size;
|
||||
const mempoolTx = mempool[tx.txid];
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = tx.score;
|
||||
mempoolTx.ancestors = (Array.from(tx.ancestorMap?.values()) as AuditTransaction[]).map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
});
|
||||
mempoolTx.bestDescendant = null;
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(tx);
|
||||
tx.used = true;
|
||||
// pack any leftover transactions into the last block
|
||||
for (const tx of overflow) {
|
||||
if (!tx || tx?.used) {
|
||||
continue;
|
||||
}
|
||||
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
||||
restOfArray.forEach(tx => {
|
||||
blockWeight += tx.weight;
|
||||
blockSize += tx.size;
|
||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||
tx.cpfpChecked = false;
|
||||
tx.ancestors = [];
|
||||
tx.bestDescendant = null;
|
||||
blockTransactions.push(tx);
|
||||
});
|
||||
if (blockTransactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(blockTransactions, blockSize, blockWeight, blocks.length));
|
||||
blockWeight += tx.weight;
|
||||
const mempoolTx = mempool[tx.txid];
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = tx.score;
|
||||
if (tx.ancestorMap.size > 0) {
|
||||
cpfpClusters[tx.txid] = Array.from(tx.ancestorMap?.values()).map(a => a.txid);
|
||||
mempoolTx.cpfpRoot = tx.txid;
|
||||
}
|
||||
transactions = [];
|
||||
} else if (transactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(tx);
|
||||
tx.used = true;
|
||||
}
|
||||
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
||||
restOfArray.forEach(tx => {
|
||||
blockWeight += tx.weight;
|
||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||
tx.cpfpChecked = false;
|
||||
blockTransactions.push(tx);
|
||||
});
|
||||
if (blockTransactions.length) {
|
||||
blocks.push(blockTransactions);
|
||||
}
|
||||
transactions = [];
|
||||
|
||||
const end = Date.now();
|
||||
const time = end - start;
|
||||
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
||||
|
||||
return {
|
||||
mempool,
|
||||
blocks
|
||||
};
|
||||
return { blocks, clusters: cpfpClusters };
|
||||
}
|
||||
|
||||
// traverse in-mempool ancestors
|
||||
@@ -239,9 +218,9 @@ function setRelatives(
|
||||
mempool: { [txid: string]: AuditTransaction },
|
||||
): void {
|
||||
for (const parent of tx.vin) {
|
||||
const parentTx = mempool[parent.txid];
|
||||
if (parentTx && !tx.ancestorMap?.has(parent.txid)) {
|
||||
tx.ancestorMap.set(parent.txid, parentTx);
|
||||
const parentTx = mempool[parent];
|
||||
if (parentTx && !tx.ancestorMap?.has(parent)) {
|
||||
tx.ancestorMap.set(parent, parentTx);
|
||||
parentTx.children.add(tx);
|
||||
// visit each node only once
|
||||
if (!parentTx.relativesSet) {
|
||||
@@ -312,27 +291,4 @@ function updateDescendants(
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
let rangeLength = 4;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
}
|
||||
if (transactions.length > 4000) {
|
||||
rangeLength = 6;
|
||||
} else if (transactions.length > 10000) {
|
||||
rangeLength = 8;
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockWeight / 4,
|
||||
nTx: transactions.length,
|
||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
||||
transactionIds: transactions.map((tx) => tx.txid),
|
||||
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
|
||||
};
|
||||
}
|
||||
@@ -251,7 +251,7 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.makeBlockTemplates(newMempool, 8, null, true);
|
||||
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid));
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(newMempool);
|
||||
}
|
||||
@@ -419,7 +419,7 @@ class WebsocketHandler {
|
||||
const _memPool = memPool.getMempool();
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool, 2);
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||
}
|
||||
@@ -462,13 +462,15 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
const removed: string[] = [];
|
||||
// Update mempool to remove transactions included in the new block
|
||||
for (const txId of txIds) {
|
||||
delete _memPool[txId];
|
||||
removed.push(txId);
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool, 8, null, true);
|
||||
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
||||
export interface AuditTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
size: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
vin: IEsploraApi.Vin[];
|
||||
effectiveFeePerVsize: number;
|
||||
vin: string[];
|
||||
relativesSet: boolean;
|
||||
ancestorMap: Map<string, AuditTransaction>;
|
||||
children: Set<AuditTransaction>;
|
||||
@@ -96,6 +96,17 @@ export interface AuditTransaction {
|
||||
modifiedNode: HeapNode<AuditTransaction>;
|
||||
}
|
||||
|
||||
export interface ThreadTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
effectiveFeePerVsize?: number;
|
||||
vin: string[];
|
||||
cpfpRoot?: string;
|
||||
cpfpChecked?: boolean;
|
||||
}
|
||||
|
||||
export interface Ancestor {
|
||||
txid: string;
|
||||
weight: number;
|
||||
|
||||
@@ -44,7 +44,9 @@ class TransactionRepository {
|
||||
const [rows]: any = await DB.query(query, [txid]);
|
||||
if (rows.length) {
|
||||
rows[0].txs = JSON.parse(rows[0].txs) as Ancestor[];
|
||||
return this.convertCpfp(rows[0]);
|
||||
if (rows[0]?.txs?.length) {
|
||||
return this.convertCpfp(rows[0]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
|
||||
@@ -310,6 +310,11 @@ class LightningStatsImporter {
|
||||
* Import topology files LN historical data into the database
|
||||
*/
|
||||
async $importHistoricalLightningStats(): Promise<void> {
|
||||
if (!config.LIGHTNING.TOPOLOGY_FOLDER) {
|
||||
logger.info(`Lightning topology folder is not set. Not importing historical LN stats`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('Run the historical importer');
|
||||
try {
|
||||
let fileList: string[] = [];
|
||||
|
||||
@@ -127,7 +127,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
||||
* Thai @Gusb3ll
|
||||
* Turkish @stackmore
|
||||
* Ukrainian @volbil
|
||||
* Vietnamese @bitcoin_vietnam
|
||||
* Vietnamese @BitcoinvnNews
|
||||
* Chinese @wdljt
|
||||
* Russian @TonyCrusoe @Bitconan
|
||||
* Romanian @mirceavesa
|
||||
|
||||
@@ -137,6 +137,10 @@
|
||||
"hi": {
|
||||
"translation": "src/locale/messages.hi.xlf",
|
||||
"baseHref": "/hi/"
|
||||
},
|
||||
"lt": {
|
||||
"translation": "src/locale/messages.lt.xlf",
|
||||
"baseHref": "/lt/"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -158,6 +162,11 @@
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
{
|
||||
"input": "src/theme-contrast.scss",
|
||||
"bundleName": "contrast",
|
||||
"inject": false
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
|
||||
31
frontend/package-lock.json
generated
31
frontend/package-lock.json
generated
@@ -31,6 +31,7 @@
|
||||
"bootstrap": "~4.6.1",
|
||||
"browserify": "^17.0.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"cypress": "^12.1.0",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.4.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
@@ -57,8 +58,8 @@
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "~2.3.0",
|
||||
"cypress": "^11.2.0",
|
||||
"@cypress/schematic": "^2.4.0",
|
||||
"cypress": "^12.1.0",
|
||||
"cypress-fail-on-console-error": "~4.0.2",
|
||||
"cypress-wait-until": "^1.7.2",
|
||||
"mock-socket": "~9.1.5",
|
||||
@@ -3225,9 +3226,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/schematic": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "^0.1402.1",
|
||||
@@ -7019,9 +7020,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -7072,7 +7073,7 @@
|
||||
"cypress": "bin/cypress"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress-fail-on-console-error": {
|
||||
@@ -19345,9 +19346,9 @@
|
||||
}
|
||||
},
|
||||
"@cypress/schematic": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "^0.1402.1",
|
||||
@@ -22282,9 +22283,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"cypress": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@cypress/request": "^2.88.10",
|
||||
|
||||
@@ -109,8 +109,8 @@
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "~2.3.0",
|
||||
"cypress": "^11.2.0",
|
||||
"@cypress/schematic": "^2.4.0",
|
||||
"cypress": "^12.1.0",
|
||||
"cypress-fail-on-console-error": "~4.0.2",
|
||||
"cypress-wait-until": "^1.7.2",
|
||||
"mock-socket": "~9.1.5",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const mempoolFeeColors = [
|
||||
export const defaultMempoolFeeColors = [
|
||||
'557d00',
|
||||
'5d7d01',
|
||||
'637d02',
|
||||
@@ -31,6 +31,39 @@ export const mempoolFeeColors = [
|
||||
'b9254b',
|
||||
];
|
||||
|
||||
export const contrastMempoolFeeColors = [
|
||||
'0082e6',
|
||||
'0984df',
|
||||
'1285d9',
|
||||
'1a87d2',
|
||||
'2388cb',
|
||||
'2c8ac5',
|
||||
'358bbe',
|
||||
'3e8db7',
|
||||
'468eb0',
|
||||
'4f90aa',
|
||||
'5892a3',
|
||||
'61939c',
|
||||
'6a9596',
|
||||
'72968f',
|
||||
'7b9888',
|
||||
'849982',
|
||||
'8d9b7b',
|
||||
'959c74',
|
||||
'9e9e6e',
|
||||
'a79f67',
|
||||
'b0a160',
|
||||
'b9a35a',
|
||||
'c1a453',
|
||||
'caa64c',
|
||||
'd3a745',
|
||||
'dca93f',
|
||||
'e5aa38',
|
||||
'edac31',
|
||||
'f6ad2b',
|
||||
'ffaf24',
|
||||
];
|
||||
|
||||
export const chartColors = [
|
||||
"#D81B60",
|
||||
"#8E24AA",
|
||||
@@ -120,7 +153,7 @@ export const languages: Language[] = [
|
||||
{ code: 'he', name: 'עברית' }, // Hebrew
|
||||
{ code: 'ka', name: 'ქართული' }, // Georgian
|
||||
// { code: 'lv', name: 'Latviešu' }, // Latvian
|
||||
// { code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||
{ code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||
{ code: 'hu', name: 'Magyar' }, // Hungarian
|
||||
{ code: 'mk', name: 'Македонски' }, // Macedonian
|
||||
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SharedModule } from './shared/shared.module';
|
||||
import { StorageService } from './services/storage.service';
|
||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||
import { LanguageService } from './services/language.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
||||
@@ -30,6 +31,7 @@ const providers = [
|
||||
StorageService,
|
||||
EnterpriseService,
|
||||
LanguageService,
|
||||
ThemeService,
|
||||
ShortenStringPipe,
|
||||
FiatShortenerPipe,
|
||||
CapAddressPipe,
|
||||
|
||||
@@ -61,18 +61,18 @@
|
||||
}
|
||||
|
||||
.big-fiat {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
width: 150px;
|
||||
}
|
||||
.mobile-even tr:nth-of-type(even) {
|
||||
background-color: #181b2d;
|
||||
background-color: var(--stat-box-bg);
|
||||
}
|
||||
.mobile-even tr:nth-of-type(odd) {
|
||||
background-color: inherit;
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
}
|
||||
|
||||
.green {
|
||||
color:#28a745;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.red {
|
||||
color:#dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.grey {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Router, NavigationEnd } from '@angular/router';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -19,6 +20,7 @@ export class AppComponent implements OnInit {
|
||||
private stateService: StateService,
|
||||
private openGraphService: OpenGraphService,
|
||||
private location: Location,
|
||||
private theme: ThemeService,
|
||||
tooltipConfig: NgbTooltipConfig,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
align-items: center;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -95,23 +95,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -170,7 +170,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -238,7 +238,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -305,7 +305,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -142,7 +142,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -205,7 +205,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -296,7 +296,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -5,6 +5,8 @@ import BlockScene from './block-scene';
|
||||
import TxSprite from './tx-sprite';
|
||||
import TxView from './tx-view';
|
||||
import { Position } from './sprite-types';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-overview-graph',
|
||||
@@ -26,6 +28,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
|
||||
@ViewChild('blockCanvas')
|
||||
canvas: ElementRef<HTMLCanvasElement>;
|
||||
themeChangedSubscription: Subscription;
|
||||
|
||||
gl: WebGLRenderingContext;
|
||||
animationFrameRequest: number;
|
||||
@@ -48,6 +51,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
constructor(
|
||||
readonly ngZone: NgZone,
|
||||
readonly elRef: ElementRef,
|
||||
private themeService: ThemeService,
|
||||
) {
|
||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||
}
|
||||
@@ -59,6 +63,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
this.initCanvas();
|
||||
|
||||
this.resizeCanvas();
|
||||
|
||||
this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||
// force full re-render
|
||||
this.resizeCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
@@ -77,6 +86,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
cancelAnimationFrame(this.animationFrameRequest);
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
}
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||
this.themeChangedSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
clear(direction): void {
|
||||
@@ -193,7 +205,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
this.start();
|
||||
} else {
|
||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray });
|
||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService });
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { FastVertexArray } from './fast-vertex-array';
|
||||
import TxView from './tx-view';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { Position, Square, ViewUpdateParams } from './sprite-types';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
export default class BlockScene {
|
||||
scene: { count: number, offset: { x: number, y: number}};
|
||||
vertexArray: FastVertexArray;
|
||||
txs: { [key: string]: TxView };
|
||||
theme: ThemeService;
|
||||
orientation: string;
|
||||
flip: boolean;
|
||||
width: number;
|
||||
@@ -22,11 +24,11 @@ export default class BlockScene {
|
||||
animateUntil = 0;
|
||||
dirty: boolean;
|
||||
|
||||
constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray }:
|
||||
constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme }:
|
||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray }
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService }
|
||||
) {
|
||||
this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray });
|
||||
this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme });
|
||||
}
|
||||
|
||||
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
||||
@@ -67,7 +69,7 @@ export default class BlockScene {
|
||||
});
|
||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||
txs.forEach(tx => {
|
||||
const txView = new TxView(tx, this.vertexArray);
|
||||
const txView = new TxView(tx, this.vertexArray, this.theme);
|
||||
this.txs[tx.txid] = txView;
|
||||
this.place(txView);
|
||||
this.saveGridToScreenPosition(txView);
|
||||
@@ -114,7 +116,7 @@ export default class BlockScene {
|
||||
});
|
||||
txs.forEach(tx => {
|
||||
if (!this.txs[tx.txid]) {
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray);
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray, this.theme);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,7 +158,7 @@ export default class BlockScene {
|
||||
if (resetLayout) {
|
||||
add.forEach(tx => {
|
||||
if (!this.txs[tx.txid]) {
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray);
|
||||
this.txs[tx.txid] = new TxView(tx, this.vertexArray, this.theme);
|
||||
}
|
||||
});
|
||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
|
||||
@@ -166,7 +168,7 @@ export default class BlockScene {
|
||||
} else {
|
||||
// try to insert new txs directly
|
||||
const remaining = [];
|
||||
add.map(tx => new TxView(tx, this.vertexArray)).sort(feeRateDescending).forEach(tx => {
|
||||
add.map(tx => new TxView(tx, this.vertexArray, this.theme)).sort(feeRateDescending).forEach(tx => {
|
||||
if (!this.tryInsertByFee(tx)) {
|
||||
remaining.push(tx);
|
||||
}
|
||||
@@ -192,13 +194,14 @@ export default class BlockScene {
|
||||
this.animateUntil = Math.max(this.animateUntil, tx.setHover(value));
|
||||
}
|
||||
|
||||
private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray }:
|
||||
private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, theme }:
|
||||
{ width: number, height: number, resolution: number, blockLimit: number,
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray }
|
||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService }
|
||||
): void {
|
||||
this.orientation = orientation;
|
||||
this.flip = flip;
|
||||
this.vertexArray = vertexArray;
|
||||
this.theme = theme;
|
||||
|
||||
this.scene = {
|
||||
count: 0,
|
||||
|
||||
@@ -2,20 +2,10 @@ import TxSprite from './tx-sprite';
|
||||
import { FastVertexArray } from './fast-vertex-array';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import BlockScene from './block-scene';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
const hoverTransitionTime = 300;
|
||||
const defaultHoverColor = hexToColor('1bd8f4');
|
||||
|
||||
const feeColors = mempoolFeeColors.map(hexToColor);
|
||||
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||
const auditColors = {
|
||||
censored: hexToColor('f344df'),
|
||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||
added: hexToColor('0099ff'),
|
||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||
}
|
||||
|
||||
// convert from this class's update format to TxSprite's update format
|
||||
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
||||
@@ -37,6 +27,7 @@ export default class TxView implements TransactionStripped {
|
||||
feerate: number;
|
||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
context?: 'projected' | 'actual';
|
||||
theme: ThemeService;
|
||||
|
||||
initialised: boolean;
|
||||
vertexArray: FastVertexArray;
|
||||
@@ -49,7 +40,7 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
dirty: boolean;
|
||||
|
||||
constructor(tx: TransactionStripped, vertexArray: FastVertexArray) {
|
||||
constructor(tx: TransactionStripped, vertexArray: FastVertexArray, theme: ThemeService) {
|
||||
this.context = tx.context;
|
||||
this.txid = tx.txid;
|
||||
this.fee = tx.fee;
|
||||
@@ -59,6 +50,7 @@ export default class TxView implements TransactionStripped {
|
||||
this.status = tx.status;
|
||||
this.initialised = false;
|
||||
this.vertexArray = vertexArray;
|
||||
this.theme = theme;
|
||||
|
||||
this.hover = false;
|
||||
|
||||
@@ -131,10 +123,10 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
// Temporarily override the tx color
|
||||
// returns minimum transition end time
|
||||
setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): number {
|
||||
setHover(hoverOn: boolean, color: Color | void): number {
|
||||
if (hoverOn) {
|
||||
this.hover = true;
|
||||
this.hoverColor = color;
|
||||
this.hoverColor = color || this.theme.defaultHoverColor;
|
||||
|
||||
this.sprite.update({
|
||||
...this.hoverColor,
|
||||
@@ -155,22 +147,22 @@ export default class TxView implements TransactionStripped {
|
||||
|
||||
getColor(): Color {
|
||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1;
|
||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
||||
const feeLevelColor = this.theme.feeColors[feeLevelIndex] || this.theme.feeColors[this.theme.mempoolFeeColors.length - 1];
|
||||
// Block audit
|
||||
switch(this.status) {
|
||||
case 'censored':
|
||||
return auditColors.censored;
|
||||
return this.theme.auditColors.censored;
|
||||
case 'missing':
|
||||
return auditColors.missing;
|
||||
return this.theme.auditColors.missing;
|
||||
case 'fresh':
|
||||
return auditColors.missing;
|
||||
return this.theme.auditColors.missing;
|
||||
case 'added':
|
||||
return auditColors.added;
|
||||
return this.theme.auditColors.added;
|
||||
case 'selected':
|
||||
return auditColors.selected;
|
||||
return this.theme.auditColors.selected;
|
||||
case 'found':
|
||||
if (this.context === 'projected') {
|
||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||
return this.theme.auditFeeColors[feeLevelIndex] || this.theme.auditFeeColors[this.theme.mempoolFeeColors.length - 1];
|
||||
} else {
|
||||
return feeLevelColor;
|
||||
}
|
||||
@@ -179,31 +171,3 @@ export default class TxView implements TransactionStripped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hexToColor(hex: string): Color {
|
||||
return {
|
||||
r: parseInt(hex.slice(0, 2), 16) / 255,
|
||||
g: parseInt(hex.slice(2, 4), 16) / 255,
|
||||
b: parseInt(hex.slice(4, 6), 16) / 255,
|
||||
a: 1
|
||||
};
|
||||
}
|
||||
|
||||
function desaturate(color: Color, amount: number): Color {
|
||||
const gray = (color.r + color.g + color.b) / 6;
|
||||
return {
|
||||
r: color.r + ((gray - color.r) * amount),
|
||||
g: color.g + ((gray - color.g) * amount),
|
||||
b: color.b + ((gray - color.b) * amount),
|
||||
a: color.a,
|
||||
};
|
||||
}
|
||||
|
||||
function darken(color: Color, amount: number): Color {
|
||||
return {
|
||||
r: color.r * amount,
|
||||
g: color.g * amount,
|
||||
b: color.b * amount,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
background: rgba(#11131f, 0.95);
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||
color: #b1b1b1;
|
||||
color: var(--tooltip-grey);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -130,7 +130,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -177,7 +177,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -289,7 +289,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -142,7 +142,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -211,7 +211,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -307,7 +307,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -141,7 +141,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -214,7 +214,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -236,7 +236,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 1,
|
||||
width: 1,
|
||||
},
|
||||
@@ -314,7 +314,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -56,3 +56,7 @@
|
||||
::ng-deep .symbol {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
|
||||
<div class="box" *ngIf="!error">
|
||||
<div class="row">
|
||||
<ng-template [ngIf]="!isLoadingBlock">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngIf="!isLoadingBlock; else skeletonRows">
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.hash">Hash</td>
|
||||
<td>‎<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
||||
@@ -54,83 +54,28 @@
|
||||
<td i18n="block.weight">Weight</td>
|
||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="auditEnabled">
|
||||
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||
<td i18n="block.health">Block health</td>
|
||||
<td>
|
||||
<span *ngIf="blockAudit?.matchRate != null">{{ blockAudit.matchRate }}%</span>
|
||||
<span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
|
||||
<span
|
||||
class="health-badge badge"
|
||||
[class.badge-success]="blockAudit?.matchRate >= 99"
|
||||
[class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99"
|
||||
[class.badge-danger]="blockAudit?.matchRate < 75"
|
||||
*ngIf="blockAudit?.matchRate != null; else nullHealth"
|
||||
>{{ blockAudit?.matchRate }}%</span>
|
||||
<ng-template #nullHealth>
|
||||
<ng-container *ngIf="!isLoadingAudit; else loadingHealth">
|
||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #loadingHealth>
|
||||
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-container *ngIf="webGlEnabled && (auditDataMissing || !indexingAvailable)">
|
||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="isLoadingBlock">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
</ng-container>
|
||||
<ng-template #skeletonRows>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
@@ -143,114 +88,18 @@
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<ng-container *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (!auditDataMissing || indexingAvailable && !webGlEnabled)">
|
||||
<tbody>
|
||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-container *ngIf="isMobile || (webGlEnabled && (auditDataMissing || !indexingAvailable)); then restOfTable;"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-borderless table-striped" *ngIf="isLoadingBlock && !auditDataMissing && (indexingAvailable || !webGlEnabled)">
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && (auditDataMissing || !indexingAvailable))">
|
||||
<tbody>
|
||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<ng-container *ngTemplateOutlet="restOfTable"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
||||
@@ -268,6 +117,87 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #restOfTable>
|
||||
<ng-container *ngIf="!isLoadingBlock; else loadingRest">
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template #loadingRest>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<span id="overview"></span>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -118,9 +118,7 @@ h1 {
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1ad8f4;
|
||||
&:hover, &:focus {
|
||||
color: #09a3ba;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -196,7 +194,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: #24273e;
|
||||
background: var(--box-bg);
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
|
||||
@@ -43,6 +43,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
strippedTransactions: TransactionStripped[];
|
||||
overviewTransitionDirection: string;
|
||||
isLoadingOverview = true;
|
||||
isLoadingAudit = true;
|
||||
error: any;
|
||||
blockSubsidy: number;
|
||||
fees: number;
|
||||
@@ -297,13 +298,18 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.auditSubscription = block$.pipe(
|
||||
startWith(null),
|
||||
pairwise(),
|
||||
switchMap(([prevBlock, block]) => this.apiService.getBlockAudit$(block.id)
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.overviewError = err;
|
||||
return of([]);
|
||||
})
|
||||
)
|
||||
switchMap(([prevBlock, block]) => {
|
||||
this.isLoadingAudit = true;
|
||||
this.blockAudit = null;
|
||||
return this.apiService.getBlockAudit$(block.id)
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.overviewError = err;
|
||||
this.isLoadingAudit = false;
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
),
|
||||
filter((response) => response != null),
|
||||
map((response) => {
|
||||
@@ -375,12 +381,14 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
console.log(err);
|
||||
this.error = err;
|
||||
this.isLoadingOverview = false;
|
||||
this.isLoadingAudit = false;
|
||||
return of(null);
|
||||
}),
|
||||
).subscribe((blockAudit) => {
|
||||
this.blockAudit = blockAudit;
|
||||
this.setupBlockGraphs();
|
||||
this.isLoadingOverview = false;
|
||||
this.isLoadingAudit = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
timeLtr: boolean;
|
||||
|
||||
gradientColors = {
|
||||
'': ['#9339f4', '#105fb0'],
|
||||
bisq: ['#9339f4', '#105fb0'],
|
||||
liquid: ['#116761', '#183550'],
|
||||
'liquidtestnet': ['#494a4a', '#272e46'],
|
||||
testnet: ['#1d486f', '#183550'],
|
||||
signet: ['#6f1d5d', '#471850'],
|
||||
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||
bisq: ['var(--mainnet-alt)', 'var(--primary)'],
|
||||
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -181,7 +181,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
||||
background: `repeating-linear-gradient(
|
||||
#2d3348,
|
||||
#2d3348 ${greenBackgroundHeight}%,
|
||||
var(--secondary) ${greenBackgroundHeight}%,
|
||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||
${this.gradientColors[this.network][1]} 100%
|
||||
)`,
|
||||
@@ -198,7 +198,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
return {
|
||||
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
||||
background: "#2d3348",
|
||||
background: "var(--secondary)",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
|
||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
|
||||
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Mined</th>
|
||||
<th *ngIf="indexingAvailable" class="health text-left" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
<th *ngIf="indexingAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
|
||||
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
|
||||
@@ -27,7 +27,7 @@
|
||||
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||
<td class="text-left" [class]="widget ? 'widget' : ''">
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a>
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<div class="tooltip-custom">
|
||||
@@ -46,16 +46,23 @@
|
||||
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null">
|
||||
<div class="progress progress-health">
|
||||
<div class="progress-bar progress-bar-health" role="progressbar"
|
||||
[ngStyle]="{'width': (100 - (auditScores[block.id] || 0)) + '%' }"></div>
|
||||
<div class="progress-text">
|
||||
<span *ngIf="auditScores[block.id] != null;">{{ auditScores[block.id] }}%</span>
|
||||
<span *ngIf="auditScores[block.id] == null">~</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="health-badge badge"
|
||||
[class.badge-success]="auditScores[block.id] >= 99"
|
||||
[class.badge-warning]="auditScores[block.id] >= 75 && auditScores[block.id] < 99"
|
||||
[class.badge-danger]="auditScores[block.id] < 75"
|
||||
[routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null"
|
||||
[state]="{ data: { block: block } }"
|
||||
*ngIf="auditScores[block.id] != null; else nullHealth"
|
||||
>{{ auditScores[block.id] }}%</a>
|
||||
<ng-template #nullHealth>
|
||||
<ng-container *ngIf="!loadingScores; else loadingHealth">
|
||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #loadingHealth>
|
||||
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
||||
|
||||
@@ -36,7 +36,7 @@ tr, td, th {
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.pool {
|
||||
|
||||
@@ -23,6 +23,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
||||
|
||||
indexingAvailable = false;
|
||||
isLoading = true;
|
||||
loadingScores = true;
|
||||
fromBlockHeight = undefined;
|
||||
paginationMaxSize: number;
|
||||
page = 1;
|
||||
@@ -113,6 +114,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
||||
if (this.indexingAvailable) {
|
||||
this.auditScoreSubscription = this.fromHeightSubject.pipe(
|
||||
switchMap((fromBlockHeight) => {
|
||||
this.loadingScores = true;
|
||||
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
@@ -124,6 +126,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
||||
Object.values(scores).forEach(score => {
|
||||
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
||||
});
|
||||
this.loadingScores = false;
|
||||
});
|
||||
|
||||
this.latestScoreSubscription = this.stateService.blocks$.pipe(
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
|
||||
<div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
|
||||
<div class="progress small-bar">
|
||||
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"> </div>
|
||||
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: var(--primary)" [ngStyle]="{'width': epochData.base}"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" *ngIf="showHalving">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
justify-content: space-around;
|
||||
height: 76px;
|
||||
.shared-block {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
.item {
|
||||
@@ -77,19 +77,19 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
@@ -43,24 +43,24 @@ export class DifficultyComponent implements OnInit {
|
||||
])
|
||||
.pipe(
|
||||
map(([block, da]) => {
|
||||
let colorAdjustments = '#ffffff66';
|
||||
let colorAdjustments = 'var(--transparent-fg)';
|
||||
if (da.difficultyChange > 0) {
|
||||
colorAdjustments = '#3bcc49';
|
||||
colorAdjustments = 'var(--green)';
|
||||
}
|
||||
if (da.difficultyChange < 0) {
|
||||
colorAdjustments = '#dc3545';
|
||||
colorAdjustments = 'var(--red)';
|
||||
}
|
||||
|
||||
let colorPreviousAdjustments = '#dc3545';
|
||||
let colorPreviousAdjustments = 'var(--red)';
|
||||
if (da.previousRetarget) {
|
||||
if (da.previousRetarget >= 0) {
|
||||
colorPreviousAdjustments = '#3bcc49';
|
||||
colorPreviousAdjustments = 'var(--green)';
|
||||
}
|
||||
if (da.previousRetarget === 0) {
|
||||
colorPreviousAdjustments = '#ffffff66';
|
||||
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||
}
|
||||
} else {
|
||||
colorPreviousAdjustments = '#ffffff66';
|
||||
colorPreviousAdjustments = 'var(--transparent-fg)';
|
||||
}
|
||||
|
||||
const blocksUntilHalving = 210000 - (block.height % 210000);
|
||||
|
||||
@@ -45,7 +45,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
@@ -36,7 +36,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
@@ -79,6 +79,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: background-color 1s;
|
||||
color: var(--color-fg);
|
||||
&.priority {
|
||||
@media (767px < width < 992px), (width < 576px) {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectionStrategy, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fees-box',
|
||||
@@ -11,14 +12,18 @@ import { tap } from 'rxjs/operators';
|
||||
styleUrls: ['./fees-box.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FeesBoxComponent implements OnInit {
|
||||
export class FeesBoxComponent implements OnInit, OnDestroy {
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
recommendedFees$: Observable<Recommendedfees>;
|
||||
themeSubscription: Subscription;
|
||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||
noPriority = '#2e324e';
|
||||
fees: Recommendedfees;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService
|
||||
private stateService: StateService,
|
||||
private themeService: ThemeService,
|
||||
private cd: ChangeDetectorRef,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -26,18 +31,32 @@ export class FeesBoxComponent implements OnInit {
|
||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||
.pipe(
|
||||
tap((fees) => {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const startColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const endColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||
this.noPriority = startColor;
|
||||
this.fees = fees;
|
||||
this.setFeeGradient();
|
||||
}
|
||||
)
|
||||
);
|
||||
this.themeSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||
this.setFeeGradient();
|
||||
})
|
||||
}
|
||||
|
||||
setFeeGradient() {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => this.fees.minimumFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const startColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => this.fees.fastestFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const endColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
|
||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||
this.noPriority = startColor;
|
||||
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.themeSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
box-shadow: 15px 15px 15px 15px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -34,16 +34,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.txPerSecond {
|
||||
color: #4a9ff4;
|
||||
}
|
||||
|
||||
.mempoolSize {
|
||||
color: #4a68b9;
|
||||
}
|
||||
|
||||
.unconfirmedTx {
|
||||
color: #f14d80;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
|
||||
.info-block {
|
||||
@@ -55,7 +47,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 160px;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,12 +115,12 @@
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -325,7 +325,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
},
|
||||
@@ -436,7 +436,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 30;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -189,7 +189,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
@@ -271,7 +271,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 30;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -151,7 +151,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.mempoolStatsChartOption.grid.height = prevHeight + 20;
|
||||
this.mempoolStatsChartOption.backgroundColor = '#11131f';
|
||||
this.mempoolStatsChartOption.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.mempoolStatsChartOption);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -99,7 +99,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
type: 'line',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: #116761;"></span>`;
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: var(--liquid);"></span>`;
|
||||
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
|
||||
params.map((item: any, index: number) => {
|
||||
if (index < 26) {
|
||||
@@ -131,7 +131,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -145,11 +145,11 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
||||
showSymbol: false,
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
color: '#116761',
|
||||
color: 'var(--liquid)',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#116761',
|
||||
color: 'var(--liquid)',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -34,7 +34,7 @@ li.nav-item {
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
background: #212121;
|
||||
background: var(--navbar-bg);
|
||||
bottom: 0;
|
||||
box-shadow: 0px 0px 15px 0px #000;
|
||||
flex-direction: row;
|
||||
@@ -95,23 +95,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #11131f;
|
||||
background: var(--active-bg);
|
||||
text-align: start;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
fa-icon {
|
||||
@@ -40,7 +40,7 @@ li.nav-item {
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
background: #212121;
|
||||
background: var(--navbar-bg);
|
||||
bottom: 0;
|
||||
box-shadow: 0px 0px 15px 0px #000;
|
||||
flex-direction: row;
|
||||
@@ -110,23 +110,23 @@ nav {
|
||||
}
|
||||
|
||||
.mainnet.active {
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.liquid.active {
|
||||
background-color: #116761;
|
||||
background-color: var(--liquid);
|
||||
}
|
||||
|
||||
.liquidtestnet.active {
|
||||
background-color: #494a4a;
|
||||
background-color: var(--liquidtestnet);
|
||||
}
|
||||
|
||||
.testnet.active {
|
||||
background-color: #1d486f;
|
||||
background-color: var(--testnet);
|
||||
}
|
||||
|
||||
.signet.active {
|
||||
background-color: #6f1d5d;
|
||||
background-color: var(--signet);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
background-color: var(--active-bg);
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { MempoolBlock } from '../../interfaces/websocket.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { take, map, switchMap } from 'rxjs/operators';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { feeLevels } from '../../app.constants';
|
||||
import { specialBlocks } from '../../app.constants';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Location } from '@angular/common';
|
||||
import { DifficultyAdjustment } from '../../interfaces/node-api.interface';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-blocks',
|
||||
@@ -58,6 +59,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private router: Router,
|
||||
public stateService: StateService,
|
||||
private themeService: ThemeService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private location: Location
|
||||
@@ -245,7 +247,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
trimmedFeeRange.forEach((fee: number) => {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
gradientColors.push(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
gradientColors.push(this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]);
|
||||
});
|
||||
|
||||
gradientColors.forEach((color, i, gc) => {
|
||||
|
||||
@@ -347,7 +347,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
color: 'var(--transparent-fg)',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
@@ -396,7 +396,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.mempoolVsizeFeesOptions.grid.height = prevHeight + 20;
|
||||
this.mempoolVsizeFeesOptions.backgroundColor = '#11131f';
|
||||
this.mempoolVsizeFeesOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<app-blocks-list [attr.data-cy]="'latest-blocks'" [widget]=true></app-blocks-list>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.adjustments">Adjustments</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<app-difficulty-adjustments-table [attr.data-cy]="'difficulty-adjustments-table'"></app-difficulty-adjustments-table>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
.graph-card {
|
||||
@@ -23,10 +23,10 @@
|
||||
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
.card-title > a {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
}
|
||||
|
||||
.card-body.pool-ranking {
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -126,7 +126,7 @@
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
name: pool.name + ((isMobile() || this.widget) ? `` : ` (${pool.share}%)`),
|
||||
label: {
|
||||
overflow: 'none',
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
alignTo: 'edge',
|
||||
edgeDistance: edgeDistance,
|
||||
},
|
||||
@@ -154,7 +154,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
@@ -182,7 +182,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||
label: {
|
||||
overflow: 'none',
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
alignTo: 'edge',
|
||||
edgeDistance: edgeDistance
|
||||
},
|
||||
@@ -191,7 +191,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
@@ -301,7 +301,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
|
||||
onSaveChart() {
|
||||
const now = new Date();
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-left: 15px;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 0.75rem;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
@@ -43,7 +43,7 @@
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 315px;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -65,7 +65,7 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: #24273e;
|
||||
background: var(--box-bg);
|
||||
|
||||
&.noimg {
|
||||
opacity: 0;
|
||||
|
||||
@@ -82,7 +82,7 @@ div.scrollable {
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.coinbase {
|
||||
@@ -184,7 +184,7 @@ div.scrollable {
|
||||
}
|
||||
|
||||
.block-count-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
@media (max-width: 767.98px) {
|
||||
|
||||
@@ -145,7 +145,7 @@ export class PoolComponent implements OnInit {
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
color: 'var(--tooltip-grey)',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
@@ -50,7 +50,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
}
|
||||
.inactive {
|
||||
.square {
|
||||
background-color: #ffffff66 !important;
|
||||
background-color: var(--transparent-fg) !important;
|
||||
}
|
||||
.fee-text {
|
||||
text-decoration: line-through;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div [formGroup]="themeForm" class="text-small text-center ml-2">
|
||||
<select formControlName="theme" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 160px;" (change)="changeTheme()">
|
||||
<option value="default" i18n="theme.mempool-theme">Mempool Theme</option>
|
||||
<option value="contrast" i18n="theme.high-contrast">High Contrast</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-selector',
|
||||
templateUrl: './theme-selector.component.html',
|
||||
styleUrls: ['./theme-selector.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ThemeSelectorComponent implements OnInit {
|
||||
themeForm: UntypedFormGroup;
|
||||
themes = ['default', 'contrast'];
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private themeService: ThemeService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.themeForm = this.formBuilder.group({
|
||||
theme: ['default']
|
||||
});
|
||||
this.themeForm.get('theme')?.setValue(this.themeService.theme);
|
||||
}
|
||||
|
||||
changeTheme() {
|
||||
const newTheme = this.themeForm.get('theme')?.value;
|
||||
this.themeService.apply(newTheme);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
}
|
||||
|
||||
&.pair > *:first-child {
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
.graph-wrapper {
|
||||
position: relative;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 10px 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
}
|
||||
|
||||
.arrow-green {
|
||||
color: #1a9436;
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.arrow-red {
|
||||
color: #dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.container-xl {
|
||||
@@ -85,7 +85,7 @@
|
||||
.graph-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
padding: 10px 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
}
|
||||
}
|
||||
.green {
|
||||
color:#28a745;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.red {
|
||||
color:#dc3545;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.grey {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.mobile-bottomcol {
|
||||
@@ -56,7 +56,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
.sats {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@
|
||||
padding: 0.75rem;
|
||||
font-size: 12px;
|
||||
&:first-child {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
white-space: pre-wrap;
|
||||
@media (min-width: 476px) {
|
||||
white-space: nowrap;
|
||||
@@ -123,7 +123,7 @@ h2 {
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #181b2d;
|
||||
background-color: var(--stat-box-bg);
|
||||
}
|
||||
|
||||
.summary {
|
||||
@@ -142,7 +142,7 @@ h2 {
|
||||
}
|
||||
|
||||
.grey-info-text {
|
||||
color:#6c757d;
|
||||
color: var(--grey);
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
filter(() => this.stateService.env.LIGHTNING),
|
||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||
tap((channels) => {
|
||||
if (!this.transactions) {
|
||||
return;
|
||||
}
|
||||
const transactions = this.transactions.filter((tx) => !tx._channels);
|
||||
channels.forEach((channel, i) => {
|
||||
transactions[i]._channels = channel;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
background: rgba(#11131f, 0.95);
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||
color: #b1b1b1;
|
||||
color: var(--tooltip-grey);
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -69,12 +69,12 @@
|
||||
<linearGradient id="input-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="2%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="30%" stop-color="#1bd8f4" />
|
||||
<stop offset="30%" stop-color="var(--info)" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[1]" />
|
||||
</linearGradient>
|
||||
<linearGradient id="output-highlight-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||
<stop offset="70%" stop-color="#1bd8f4" />
|
||||
<stop offset="70%" stop-color="var(--info)" />
|
||||
<stop offset="98%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||
</linearGradient>
|
||||
@@ -88,7 +88,7 @@
|
||||
<stop offset="100%" stop-color="transparent" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||
<path *ngIf="hasLine" [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||
<ng-container *ngFor="let input of inputs; let i = index">
|
||||
<path *ngIf="connectors && !inputData[i].coinbase && !inputData[i].pegin"
|
||||
[attr.d]="input.connectorPath"
|
||||
@@ -106,7 +106,7 @@
|
||||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
<path
|
||||
<path *ngIf="!input.zeroValue"
|
||||
[attr.d]="input.path"
|
||||
class="line {{input.class}}"
|
||||
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||
@@ -116,6 +116,16 @@
|
||||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
<path *ngIf="input.zeroValue"
|
||||
[attr.d]="input.path"
|
||||
class="line {{input.class}} zerovalue"
|
||||
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||
[class.zerovalue]="input.zeroValue"
|
||||
[style]="input.style"
|
||||
(pointerover)="onHover($event, 'input', i);"
|
||||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let output of outputs; let i = index">
|
||||
<path *ngIf="connectors && outspends[outputData[i].index]?.spent"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
stroke: url(#output-highlight-gradient);
|
||||
}
|
||||
&.zerovalue {
|
||||
stroke: #1bd8f4;
|
||||
stroke: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
outspends: Outspend[] = [];
|
||||
zeroValueWidth = 60;
|
||||
zeroValueThickness = 20;
|
||||
hasLine: boolean;
|
||||
|
||||
outspendsSubscription: Subscription;
|
||||
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||
@@ -162,7 +163,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
let truncatedInputs = this.tx.vin.map((v, i) => {
|
||||
return {
|
||||
type: 'input',
|
||||
value: v?.prevout?.value,
|
||||
value: v?.prevout?.value || (v?.is_coinbase && !totalValue ? 0 : undefined),
|
||||
txid: v.txid,
|
||||
vout: v.vout,
|
||||
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
||||
@@ -198,6 +199,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
||||
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
||||
};
|
||||
|
||||
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
||||
&& this.outputs.reduce((line, put) => line || !put.zeroValue, false);
|
||||
}
|
||||
|
||||
calcTotalValue(tx: Transaction): number {
|
||||
@@ -278,6 +282,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
lineParams.forEach((line, i) => {
|
||||
if (xputs[i].value === 0) {
|
||||
line.outerY = lastOuter + (this.zeroValueThickness / 2);
|
||||
if (xputs.length === 1) {
|
||||
line.outerY = (this.height / 2);
|
||||
}
|
||||
lastOuter += this.zeroValueThickness + spacing;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: 'var(--title-fg)'"></fa-icon>
|
||||
</a>
|
||||
<table class="table lastest-blocks-table">
|
||||
<thead>
|
||||
@@ -140,7 +140,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-language-selector></app-language-selector>
|
||||
<div class="pref-selectors">
|
||||
<app-language-selector></app-language-selector>
|
||||
<app-theme-selector></app-theme-selector>
|
||||
</div>
|
||||
|
||||
<div class="terms-of-service">
|
||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
height: 1.1rem;
|
||||
max-width: 180px;
|
||||
}
|
||||
@@ -98,7 +98,7 @@
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -323,4 +323,11 @@
|
||||
margin-bottom: 10px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.pref-selectors {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
p {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-weight: 700;
|
||||
margin: 10px 0;
|
||||
margin: 15px 0 10px 0;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ li.nav-item {
|
||||
.nav-tabs .nav-link.active {
|
||||
border-bottom: 1px solid #fff;
|
||||
@media (min-width: 676px){
|
||||
border-bottom: 1px solid #11131f;
|
||||
border-bottom: 1px solid var(--active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ li.nav-item {
|
||||
padding: 0px;
|
||||
}
|
||||
.nav-tabs .nav-link.active {
|
||||
border-bottom: 1px solid #11131f;
|
||||
border-bottom: 1px solid var(--active-bg);
|
||||
}
|
||||
.subtitle {
|
||||
display: flex;
|
||||
@@ -110,17 +110,17 @@ li.nav-item {
|
||||
top: 20px;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 50px);
|
||||
scrollbar-color: #2d3348 #11131f;
|
||||
scrollbar-color: var(--secondary) var(--active-bg);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #11131f;
|
||||
background: var(--active-bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
}
|
||||
@@ -148,8 +148,8 @@ h3 {
|
||||
|
||||
.endpoint-container .section-header {
|
||||
display: block;
|
||||
background-color: #2d3348;
|
||||
color: #1bd8f4;
|
||||
background-color: var(--secondary);
|
||||
color: var(--info);
|
||||
padding: 1rem 1.3rem 1rem 1.3rem;
|
||||
font-weight: bold;
|
||||
border-radius: 0.25rem;
|
||||
@@ -163,7 +163,7 @@ h3 {
|
||||
|
||||
.endpoint-container .section-header span {
|
||||
color: #fff;
|
||||
background-color: #653b9c;
|
||||
background-color: var(--tertiary);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
@@ -195,7 +195,7 @@ h3 {
|
||||
}
|
||||
|
||||
#doc-nav-mobile > div {
|
||||
background-color: #2d3348;
|
||||
background-color: var(--secondary);
|
||||
z-index: 100;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
height: 55vh;
|
||||
@@ -204,9 +204,9 @@ h3 {
|
||||
|
||||
#doc-nav-mobile button {
|
||||
width: 100%;
|
||||
background-color: #105fb0;
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
border-color: #105fb0;
|
||||
border-color: var(--primary);
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ h3 {
|
||||
}
|
||||
|
||||
#disclaimer {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
padding: 24px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ li.nav-item {
|
||||
.nav-tabs .nav-link.active {
|
||||
border-bottom: 1px solid #fff;
|
||||
@media (min-width: 676px){
|
||||
border-bottom: 1px solid #11131f;
|
||||
border-bottom: 1px solid var(--active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ li.nav-item {
|
||||
padding: 0px;
|
||||
}
|
||||
.nav-tabs .nav-link.active {
|
||||
border-bottom: 1px solid #11131f;
|
||||
border-bottom: 1px solid var(--active-bg);
|
||||
}
|
||||
.subtitle {
|
||||
display: flex;
|
||||
@@ -87,7 +87,7 @@ pre {
|
||||
display: block;
|
||||
font-size: 87.5%;
|
||||
color: #f18920;
|
||||
background-color: #1d1f31;
|
||||
background-color: var(--bg);
|
||||
padding: 30px;
|
||||
code{
|
||||
background-color: transparent;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.shared-block {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.row.nodes {
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
margin: 15px 0 0;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
width: 470px;
|
||||
min-width: 470px;
|
||||
padding: 0;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
max-height: 350px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="container-xl" *ngIf="(channel$ | async) as channel; else skeletonLoader">
|
||||
<h5 class="mb-0" style="color: #ffffff66" i18n="lightning.channel">Lightning channel</h5>
|
||||
<h5 class="mb-0" style="color: var(--transparent-fg)" i18n="lightning.channel">Lightning channel</h5>
|
||||
<div class="title-container">
|
||||
<h1 class="mb-0">{{ channel.short_id }}</h1>
|
||||
<span class="tx-link">
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
<ng-template #skeletonLoader>
|
||||
<div class="container-xl">
|
||||
<h5 class="mb-0" style="color: #ffffff66" i18n="lightning.channel">Lightning channel</h5>
|
||||
<h5 class="mb-0" style="color: var(--transparent-fg)" i18n="lightning.channel">Lightning channel</h5>
|
||||
<div class="title-container">
|
||||
<h1 class="mb-0"><span class="skeleton-loader" style="width: 275px; height: 25px;"></span></h1>
|
||||
<span class="tx-link">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.sats {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="widget-toggler">
|
||||
<a href="" (click)="switchMode('avg')" class="toggler-option"
|
||||
[ngClass]="{'inactive': mode === 'avg'}"><small>avg</small></a>
|
||||
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||
<span style="color: var(--transparent-fg); font-size: 8px"> | </span>
|
||||
<a href="" (click)="switchMode('med')" class="toggler-option"
|
||||
[ngClass]="{'inactive': mode === 'med'}"><small>med</small></a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
color: var(--title-fg);
|
||||
font-size: 10px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1rem;
|
||||
@@ -54,7 +54,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.card-text span {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
font-size: 12px;
|
||||
top: 0px;
|
||||
}
|
||||
@@ -107,5 +107,5 @@
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: #ffffff66;
|
||||
color: var(--transparent-fg);
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
width: 100%;
|
||||
margin: 16px 0 0;
|
||||
padding: 20px 12px;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
min-height: 272px;
|
||||
max-height: 272px;
|
||||
padding: 0;
|
||||
background: #181b2d;
|
||||
background: var(--stat-box-bg);
|
||||
overflow: hidden;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="container-xl full-height" style="min-height: 335px">
|
||||
<h5 class="mb-1" style="color: #ffffff66" i18n="lightning.node-group">Lightning node group</h5>
|
||||
<h5 class="mb-1" style="color: var(--transparent-fg)" i18n="lightning.node-group">Lightning node group</h5>
|
||||
|
||||
<div class="header">
|
||||
<div class="logo-container">
|
||||
@@ -44,7 +44,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 p-3 p-md-0 pr-md-3">
|
||||
<div style="background-color: #181b2d">
|
||||
<div style="background-color: var(--stat-box-bg)">
|
||||
<app-nodes-map [widget]="true" [nodes]="nodes.nodes" type="isp"></app-nodes-map>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user