Merge branch 'master' into nymkappa/unix-socket
This commit is contained in:
		
						commit
						ba8cca6ba5
					
				| @ -1,12 +1,14 @@ | ||||
| import { Application, Request, Response } from "express"; | ||||
| import config from "../../config"; | ||||
| import axios from "axios"; | ||||
| import logger from "../../logger"; | ||||
| import { Application, Request, Response } from 'express'; | ||||
| import config from '../../config'; | ||||
| import axios from 'axios'; | ||||
| import logger from '../../logger'; | ||||
| import mempool from '../mempool'; | ||||
| import AccelerationRepository from '../../repositories/AccelerationRepository'; | ||||
| 
 | ||||
| class AccelerationRoutes { | ||||
|   private tag = 'Accelerator'; | ||||
| 
 | ||||
|   public initRoutes(app: Application) { | ||||
|   public initRoutes(app: Application): void { | ||||
|     app | ||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this)) | ||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this)) | ||||
| @ -15,41 +17,33 @@ class AccelerationRoutes { | ||||
|     ; | ||||
|   } | ||||
| 
 | ||||
|   private async $getAcceleratorAccelerations(req: Request, res: Response) { | ||||
|     const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; | ||||
|     try { | ||||
|       const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); | ||||
|       for (const key in response.headers) { | ||||
|         res.setHeader(key, response.headers[key]);  | ||||
|       }       | ||||
|       response.data.pipe(res); | ||||
|     } catch (e) { | ||||
|       logger.err(`Unable to get current accelerations from ${url} in $getAcceleratorAccelerations(), ${e}`, this.tag); | ||||
|       res.status(500).end(); | ||||
|     } | ||||
|   private async $getAcceleratorAccelerations(req: Request, res: Response): Promise<void> { | ||||
|     const accelerations = mempool.getAccelerations(); | ||||
|     res.status(200).send(Object.values(accelerations)); | ||||
|   } | ||||
| 
 | ||||
|   private async $getAcceleratorAccelerationsHistory(req: Request, res: Response) { | ||||
|     const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; | ||||
|     try { | ||||
|       const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); | ||||
|       for (const key in response.headers) { | ||||
|         res.setHeader(key, response.headers[key]);  | ||||
|       }       | ||||
|       response.data.pipe(res); | ||||
|     } catch (e) { | ||||
|       logger.err(`Unable to get acceleration history from ${url} in $getAcceleratorAccelerationsHistory(), ${e}`, this.tag); | ||||
|       res.status(500).end(); | ||||
|     } | ||||
|   private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise<void> { | ||||
|     const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null); | ||||
|     res.status(200).send(history.map(accel => ({ | ||||
|       txid: accel.txid, | ||||
|       added: accel.added, | ||||
|       status: 'completed', | ||||
|       effectiveFee: accel.effective_fee, | ||||
|       effectiveVsize: accel.effective_vsize, | ||||
|       boostRate: accel.boost_rate, | ||||
|       boostCost: accel.boost_cost, | ||||
|       blockHeight: accel.height, | ||||
|       pools: [accel.pool], | ||||
|     }))); | ||||
|   } | ||||
| 
 | ||||
|   private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response) { | ||||
|   private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response): Promise<void> { | ||||
|     const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; | ||||
|     try { | ||||
|       const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); | ||||
|       for (const key in response.headers) { | ||||
|         res.setHeader(key, response.headers[key]);  | ||||
|       }       | ||||
|         res.setHeader(key, response.headers[key]); | ||||
|       } | ||||
|       response.data.pipe(res); | ||||
|     } catch (e) { | ||||
|       logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag); | ||||
| @ -57,13 +51,13 @@ class AccelerationRoutes { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async $getAcceleratorAccelerationsStats(req: Request, res: Response) { | ||||
|   private async $getAcceleratorAccelerationsStats(req: Request, res: Response): Promise<void> { | ||||
|     const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`; | ||||
|     try { | ||||
|       const response = await axios.get(url, { responseType: 'stream', timeout: 10000 }); | ||||
|       for (const key in response.headers) { | ||||
|         res.setHeader(key, response.headers[key]);  | ||||
|       }       | ||||
|         res.setHeader(key, response.headers[key]); | ||||
|       } | ||||
|       response.data.pipe(res); | ||||
|     } catch (e) { | ||||
|       logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag); | ||||
|  | ||||
| @ -29,6 +29,7 @@ import websocketHandler from './websocket-handler'; | ||||
| import redisCache from './redis-cache'; | ||||
| import rbfCache from './rbf-cache'; | ||||
| import { calcBitsDifference } from './difficulty-adjustment'; | ||||
| import AccelerationRepository from '../repositories/AccelerationRepository'; | ||||
| 
 | ||||
| class Blocks { | ||||
|   private blocks: BlockExtended[] = []; | ||||
| @ -872,6 +873,7 @@ class Blocks { | ||||
|             await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10); | ||||
|             await HashratesRepository.$deleteLastEntries(); | ||||
|             await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10); | ||||
|             await AccelerationRepository.$deleteAccelerationsFrom(lastBlock.height - 10); | ||||
|             this.blocks = this.blocks.slice(0, -10); | ||||
|             this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`); | ||||
|             for (let i = 10; i >= 0; --i) { | ||||
| @ -974,6 +976,9 @@ class Blocks { | ||||
|       if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) { | ||||
|         this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4); | ||||
|       } | ||||
|       blockSummary.transactions.forEach(tx => { | ||||
|         delete tx.acc; | ||||
|       }); | ||||
|       this.blockSummaries.push(blockSummary); | ||||
|       if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) { | ||||
|         this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4); | ||||
| @ -1117,6 +1122,7 @@ class Blocks { | ||||
|           } | ||||
|           return { | ||||
|             txid: tx.txid, | ||||
|             time: tx.firstSeen, | ||||
|             fee: tx.fee || 0, | ||||
|             vsize: tx.vsize, | ||||
|             value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0)), | ||||
|  | ||||
| @ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; | ||||
| import { RowDataPacket } from 'mysql2'; | ||||
| 
 | ||||
| class DatabaseMigration { | ||||
|   private static currentVersion = 76; | ||||
|   private static currentVersion = 77; | ||||
|   private queryTimeout = 3600_000; | ||||
|   private statisticsAddedIndexed = false; | ||||
|   private uniqueLogs: string[] = []; | ||||
| @ -664,6 +664,11 @@ class DatabaseMigration { | ||||
|       await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"'); | ||||
|       await this.updateToSchemaVersion(76); | ||||
|     } | ||||
| 
 | ||||
|     if (databaseSchemaVersion < 77 && config.MEMPOOL.NETWORK === 'mainnet') { | ||||
|       await this.$executeQuery('ALTER TABLE `accelerations` ADD requested datetime DEFAULT NULL'); | ||||
|       await this.updateToSchemaVersion(77); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | ||||
| @ -5,6 +5,9 @@ import axios from 'axios'; | ||||
| 
 | ||||
| export interface Acceleration { | ||||
|   txid: string, | ||||
|   added: number, | ||||
|   effectiveVsize: number, | ||||
|   effectiveFee: number, | ||||
|   feeDelta: number, | ||||
|   pools: number[], | ||||
| }; | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { IEsploraApi } from '../api/bitcoin/esplora-api.interface'; | ||||
| import { Common } from '../api/common'; | ||||
| import config from '../config'; | ||||
| import blocks from '../api/blocks'; | ||||
| import accelerationApi, { Acceleration } from '../api/services/acceleration'; | ||||
| import accelerationApi, { Acceleration, AccelerationHistory } from '../api/services/acceleration'; | ||||
| import accelerationCosts from '../api/acceleration/acceleration'; | ||||
| import bitcoinApi from '../api/bitcoin/bitcoin-api-factory'; | ||||
| import transactionUtils from '../api/transaction-utils'; | ||||
| @ -15,6 +15,7 @@ import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces | ||||
| export interface PublicAcceleration { | ||||
|   txid: string, | ||||
|   height: number, | ||||
|   added: number, | ||||
|   pool: { | ||||
|     id: number, | ||||
|     slug: string, | ||||
| @ -29,15 +30,20 @@ export interface PublicAcceleration { | ||||
| class AccelerationRepository { | ||||
|   private bidBoostV2Activated = 831580; | ||||
| 
 | ||||
|   public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise<void> { | ||||
|   public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise<void> { | ||||
|     const accelerationMap: { [txid: string]: Acceleration } = {}; | ||||
|     for (const acc of accelerationData) { | ||||
|       accelerationMap[acc.txid] = acc; | ||||
|     } | ||||
|     try { | ||||
|       await DB.query(` | ||||
|         INSERT INTO accelerations(txid, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost) | ||||
|         VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?) | ||||
|         INSERT INTO accelerations(txid, requested, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost) | ||||
|         VALUE (?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?) | ||||
|         ON DUPLICATE KEY UPDATE | ||||
|           height = ? | ||||
|       `, [
 | ||||
|         acceleration.txSummary.txid, | ||||
|         accelerationMap[acceleration.txSummary.txid].added, | ||||
|         block.timestamp, | ||||
|         block.height, | ||||
|         pool_id, | ||||
| @ -64,7 +70,7 @@ class AccelerationRepository { | ||||
|     } | ||||
| 
 | ||||
|     let query = ` | ||||
|       SELECT * FROM accelerations | ||||
|       SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations | ||||
|       JOIN pools on pools.unique_id = accelerations.pool | ||||
|     `;
 | ||||
|     let params: any[] = []; | ||||
| @ -99,6 +105,7 @@ class AccelerationRepository { | ||||
|         return rows.map(row => ({ | ||||
|           txid: row.txid, | ||||
|           height: row.height, | ||||
|           added: row.requested_timestamp || row.block_timestamp, | ||||
|           pool: { | ||||
|             id: row.id, | ||||
|             slug: row.slug, | ||||
| @ -202,7 +209,7 @@ class AccelerationRepository { | ||||
|         const tx = blockTxs[acc.txid]; | ||||
|         const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); | ||||
|         accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); | ||||
|         this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id); | ||||
|         this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations); | ||||
|       } | ||||
|     } | ||||
|     const lastSyncedHeight = await this.$getLastSyncedHeight(); | ||||
| @ -230,7 +237,7 @@ class AccelerationRepository { | ||||
|     logger.debug(`Fetching accelerations between block ${lastSyncedHeight} and ${currentHeight}`); | ||||
| 
 | ||||
|     // Fetch accelerations from mempool.space since the last synced block;
 | ||||
|     const accelerationsByBlock = {}; | ||||
|     const accelerationsByBlock: {[height: number]: AccelerationHistory[]} = {}; | ||||
|     const blockHashes = {}; | ||||
|     let done = false; | ||||
|     let page = 1; | ||||
| @ -297,12 +304,16 @@ class AccelerationRepository { | ||||
|           const feeStats = Common.calcEffectiveFeeStatistics(template); | ||||
|           boostRate = feeStats.medianFee; | ||||
|         } | ||||
|         const accelerationSummaries = accelerations.map(acc => ({ | ||||
|           ...acc, | ||||
|           pools: acc.pools.map(pool => pool.pool_unique_id), | ||||
|         })) | ||||
|         for (const acc of accelerations) { | ||||
|           if (blockTxs[acc.txid]) { | ||||
|             const tx = blockTxs[acc.txid]; | ||||
|             const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); | ||||
|             accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); | ||||
|             await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id); | ||||
|             await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, accelerationSummaries); | ||||
|           } | ||||
|         } | ||||
|         await this.$setLastSyncedHeight(height); | ||||
| @ -317,6 +328,26 @@ class AccelerationRepository { | ||||
| 
 | ||||
|     logger.debug(`Indexing accelerations completed`); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Delete accelerations from the database above blockHeight | ||||
|    */ | ||||
|   public async $deleteAccelerationsFrom(blockHeight: number): Promise<void> { | ||||
|     logger.info(`Delete newer accelerations from height ${blockHeight} from the database`); | ||||
|     try { | ||||
|       const currentSyncedHeight = await this.$getLastSyncedHeight(); | ||||
|       if (currentSyncedHeight >= blockHeight) { | ||||
|         await DB.query(` | ||||
|           UPDATE state | ||||
|           SET number = ? | ||||
|           WHERE name = 'last_acceleration_block' | ||||
|         `, [blockHeight - 1]);
 | ||||
|       } | ||||
|       await DB.query(`DELETE FROM accelerations where height >= ${blockHeight}`); | ||||
|     } catch (e) { | ||||
|       logger.err('Cannot delete indexed accelerations. Reason: ' + (e instanceof Error ? e.message : e)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default new AccelerationRepository(); | ||||
|  | ||||
							
								
								
									
										3
									
								
								contributors/daweilv.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								contributors/daweilv.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of April 7, 2024. | ||||
| 
 | ||||
| Signed: daweilv | ||||
| @ -170,6 +170,11 @@ | ||||
|             ], | ||||
|             "styles": [ | ||||
|               "src/styles.scss", | ||||
|               { | ||||
|                 "input": "src/theme-contrast.scss", | ||||
|                 "bundleName": "contrast", | ||||
|                 "inject": false | ||||
|               }, | ||||
|               "node_modules/@fortawesome/fontawesome-svg-core/styles.css" | ||||
|             ], | ||||
|             "vendorChunk": true, | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| export const mempoolFeeColors = [ | ||||
| export const defaultMempoolFeeColors = [ | ||||
|   '557d00', | ||||
|   '5d7d01', | ||||
|   '637d02', | ||||
| @ -39,6 +39,47 @@ export const mempoolFeeColors = [ | ||||
|   'ae005b', | ||||
| ]; | ||||
| 
 | ||||
| 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', | ||||
|   'ffb01e', | ||||
|   'ffb118', | ||||
|   'ffb212', | ||||
|   'ffb30c', | ||||
|   'ffb406', | ||||
|   'ffb500', | ||||
|   'ffb600', | ||||
|   'ffb700', | ||||
|  ]; | ||||
| 
 | ||||
| export const chartColors = [ | ||||
|   "#D81B60", | ||||
|   "#8E24AA", | ||||
|  | ||||
| @ -19,6 +19,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 { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe'; | ||||
| import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; | ||||
| @ -38,6 +39,7 @@ const providers = [ | ||||
|   StorageService, | ||||
|   EnterpriseService, | ||||
|   LanguageService, | ||||
|   ThemeService, | ||||
|   ShortenStringPipe, | ||||
|   FiatShortenerPipe, | ||||
|   FiatCurrencyPipe, | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| } | ||||
| 
 | ||||
| .become-sponsor { | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
|   border-radius: 16px; | ||||
|   padding: 12px 20px; | ||||
|   width: 400px; | ||||
|  | ||||
| @ -92,7 +92,7 @@ | ||||
| 
 | ||||
|       &.target { | ||||
|         .fill { | ||||
|           background: #653b9c; | ||||
|           background: var(--tertiary); | ||||
|         } | ||||
|         .fee { | ||||
|           position: absolute; | ||||
| @ -114,7 +114,7 @@ | ||||
|         } | ||||
|         &.active, &:hover { | ||||
|           .fill { | ||||
|             background: #105fb0; | ||||
|             background: var(--primary); | ||||
|           } | ||||
|           .line { | ||||
|             .fee-rate .label { | ||||
|  | ||||
| @ -65,24 +65,26 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|       <br> | ||||
|       <h5 i18n="accelerator.pay-how-much">How much more are you willing to pay?</h5> | ||||
|       <div class="row"> | ||||
|         <div class="col"> | ||||
|           <small class="form-text text-muted mb-2" i18n="accelerator.transaction-fee-description">Choose the maximum extra transaction fee you're willing to pay to get into the next block.</small> | ||||
|           <div class="form-group"> | ||||
|             <div class="fee-card"> | ||||
|               <div class="d-flex mb-0"> | ||||
|                 <ng-container *ngFor="let option of maxRateOptions"> | ||||
|                   <button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)"> | ||||
|                     <span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats">sats</span></span> | ||||
|                     <span class="rate">~<app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span> | ||||
|                   </button> | ||||
|                 </ng-container> | ||||
|       @if (paymentType !== 'cashapp') { | ||||
|         <h5 i18n="accelerator.pay-how-much">How much more are you willing to pay?</h5> | ||||
|         <div class="row"> | ||||
|           <div class="col"> | ||||
|             <small class="form-text text-muted mb-2" i18n="accelerator.transaction-fee-description">Choose the maximum extra transaction fee you're willing to pay to get into the next block.</small> | ||||
|             <div class="form-group"> | ||||
|               <div class="fee-card"> | ||||
|                 <div class="d-flex mb-0"> | ||||
|                   <ng-container *ngFor="let option of maxRateOptions"> | ||||
|                     <button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)"> | ||||
|                       <span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats">sats</span></span> | ||||
|                       <span class="rate">~<app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span> | ||||
|                     </button> | ||||
|                   </ng-container> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       } | ||||
|    | ||||
|       <h5>Acceleration summary</h5> | ||||
|       <div class="row mb-3"> | ||||
| @ -90,27 +92,51 @@ | ||||
|           <table class="table table-borderless table-border table-dark table-accelerator"> | ||||
|             <tbody> | ||||
|               <!-- ESTIMATED FEE --> | ||||
|               <ng-container> | ||||
|                 <tr class="group-first"> | ||||
|                   <td class="item" i18n="accelerator.next-block-rate">Next block market rate</td> | ||||
|                   <td class="amt" style="font-size: 16px"> | ||||
|                     {{ estimate.targetFeeRate | number : '1.0-0' }} | ||||
|                   </td> | ||||
|                   <td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td> | ||||
|                 </tr> | ||||
|                 <tr class="info"> | ||||
|                   <td class="info"> | ||||
|                     <i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i> | ||||
|                   </td> | ||||
|                   <td class="amt"> | ||||
|                     {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} | ||||
|                   </td> | ||||
|                   <td class="units"> | ||||
|                     <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                     <span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|               </ng-container> | ||||
|               @if (paymentType === 'cashapp') { | ||||
|                 <ng-container> | ||||
|                   <tr class="group-first"> | ||||
|                     <td class="item" i18n="accelerator.boost-rate">Boost rate</td> | ||||
|                     <td class="amt" style="font-size: 16px"> | ||||
|                       {{ maxRateOptions[selectFeeRateIndex].rate | number : '1.0-0' }} | ||||
|                     </td> | ||||
|                     <td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td> | ||||
|                   </tr> | ||||
|                   <tr class="info"> | ||||
|                     <td class="info"> | ||||
|                       <i><small i18n="accelerator.estimated-extra-fee-required">Boost fee</small></i> | ||||
|                     </td> | ||||
|                     <td class="amt"> | ||||
|                       {{ maxRateOptions[selectFeeRateIndex].fee | number }} | ||||
|                     </td> | ||||
|                     <td class="units"> | ||||
|                       <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                       <span class="fiat ml-1"><app-fiat [value]="maxRateOptions[selectFeeRateIndex].fee"></app-fiat></span> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                 </ng-container> | ||||
|               } @else { | ||||
|                 <ng-container> | ||||
|                   <tr class="group-first"> | ||||
|                     <td class="item" i18n="accelerator.next-block-rate">Next block market rate</td> | ||||
|                     <td class="amt" style="font-size: 16px"> | ||||
|                       {{ estimate.targetFeeRate | number : '1.0-0' }} | ||||
|                     </td> | ||||
|                     <td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td> | ||||
|                   </tr> | ||||
|                   <tr class="info"> | ||||
|                     <td class="info"> | ||||
|                       <i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i> | ||||
|                     </td> | ||||
|                     <td class="amt"> | ||||
|                       {{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }} | ||||
|                     </td> | ||||
|                     <td class="units"> | ||||
|                       <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                       <span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                 </ng-container> | ||||
|               } | ||||
|    | ||||
|               <!-- MEMPOOL BASE FEE --> | ||||
|               <tr> | ||||
| @ -141,53 +167,76 @@ | ||||
|                 </td> | ||||
|               </tr> | ||||
| 
 | ||||
|               <!-- NEXT BLOCK ESTIMATE --> | ||||
|               <ng-container> | ||||
|                 <tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;"> | ||||
|                   <td class="item"> | ||||
|                     <b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> | ||||
|                   </td> | ||||
|                   <td class="amt"> | ||||
|                     <span style="background-color: #5E35B1" class="p-1 pl-0"> | ||||
|                       {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} | ||||
|                     </span> | ||||
|                   </td> | ||||
|                   <td class="units"> | ||||
|                     <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                     <span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 <tr class="info group-last" style="border-bottom: 1px solid lightgrey"> | ||||
|                   <td class="info" colspan=3> | ||||
|                     <i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: estimate.targetFeeRate }"></ng-container></small></i> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|               </ng-container> | ||||
|                | ||||
|               @if (paymentType === 'cashapp') { | ||||
|                 <!-- FIXED COST --> | ||||
|                 <ng-container> | ||||
|                   <tr class="group-first group-last" style="border-top: 1px solid lightgrey; border-collapse: collapse;"> | ||||
|                     <td class="item"> | ||||
|                       <b style="background-color: #105fb0;" class="p-1 pl-0" i18n="accelerator.total-cost">Total cost</b> | ||||
|                     </td> | ||||
|                     <td class="amt"> | ||||
|                       <span style="background-color: #105fb0" class="p-1 pl-0"> | ||||
|                         {{ maxCost | number }} | ||||
|                       </span> | ||||
|                     </td> | ||||
|                     <td class="units"> | ||||
|                       <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                       <span class="fiat ml-1"> | ||||
|                         <app-fiat [value]="maxCost" [colorClass]="'green-color'"></app-fiat> | ||||
|                       </span> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                 </ng-container> | ||||
|               } @else { | ||||
|                 <!-- NEXT BLOCK ESTIMATE --> | ||||
|                 <ng-container> | ||||
|                   <tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;"> | ||||
|                     <td class="item"> | ||||
|                       <b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> | ||||
|                     </td> | ||||
|                     <td class="amt"> | ||||
|                       <span style="background-color: #5E35B1" class="p-1 pl-0"> | ||||
|                         {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} | ||||
|                       </span> | ||||
|                     </td> | ||||
|                     <td class="units"> | ||||
|                       <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                       <span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                   <tr class="info group-last" style="border-bottom: 1px solid lightgrey"> | ||||
|                     <td class="info" colspan=3> | ||||
|                       <i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: estimate.targetFeeRate }"></ng-container></small></i> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                 </ng-container> | ||||
|    | ||||
|               <!-- MAX COST --> | ||||
|               <ng-container> | ||||
|                 <tr class="group-first"> | ||||
|                   <td class="item"> | ||||
|                     <b style="background-color: #105fb0;" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b> | ||||
|                   </td> | ||||
|                   <td class="amt"> | ||||
|                     <span style="background-color: #105fb0" class="p-1 pl-0"> | ||||
|                       {{ maxCost | number }} | ||||
|                     </span> | ||||
|                   </td> | ||||
|                   <td class="units"> | ||||
|                     <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                     <span class="fiat ml-1"> | ||||
|                       <app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat> | ||||
|                     </span> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 <tr class="info group-last"> | ||||
|                   <td class="info" colspan=3> | ||||
|                     <i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: (estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize }"></ng-container></small></i> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|               </ng-container> | ||||
|                 <!-- MAX COST --> | ||||
|                 <ng-container> | ||||
|                   <tr class="group-first"> | ||||
|                     <td class="item"> | ||||
|                       <b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b> | ||||
|                     </td> | ||||
|                     <td class="amt"> | ||||
|                       <span style="background-color: var(--primary)" class="p-1 pl-0"> | ||||
|                         {{ maxCost | number }} | ||||
|                       </span> | ||||
|                     </td> | ||||
|                     <td class="units"> | ||||
|                       <span class="symbol" i18n="shared.sats">sats</span> | ||||
|                       <span class="fiat ml-1"> | ||||
|                         <app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat> | ||||
|                       </span> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                   <tr class="info group-last"> | ||||
|                     <td class="info" colspan=3> | ||||
|                       <i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: (estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize }"></ng-container></small></i> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|                 </ng-container> | ||||
|               } | ||||
|    | ||||
|               <!-- USER BALANCE --> | ||||
|               <ng-container *ngIf="isLoggedIn() && estimate.userBalance < maxCost"> | ||||
| @ -237,14 +286,17 @@ | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="row d-flex justify-content-end align-items-center mr-1" style="height: 48px" *ngIf="!hideCashApp && paymentType === 'cashapp'"> | ||||
|         <div [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'" class="p-2">Accelerate with</div> | ||||
|         <div id="cash-app-pay" style="max-width: 320px" [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'"></div> | ||||
|         <div *ngIf="showSpinner" class="d-flex align-items-center"> | ||||
|           <span class="mr-2">Loading</span> | ||||
|           <div class="spinner-border text-light" style="width: 25px; height: 25px"></div> | ||||
|       @if (!hideCashApp && paymentType === 'cashapp') { | ||||
|         <div #cashappCTA class="cashapp-placeholder {{ stickyCTA }}"></div> | ||||
|         <div class="d-flex justify-content-center align-items-center cashapp-cta {{ stickyCTA }}" (click)="submitCashappPay()"> | ||||
|           <div [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'" class="p-2">Accelerate for <app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat> with</div> | ||||
|           <div id="cash-app-pay" style="max-width: 320px" [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'"></div> | ||||
|           <div *ngIf="showSpinner" class="d-flex align-items-center"> | ||||
|             <span class="mr-2">Loading</span> | ||||
|             <div class="spinner-border text-light" style="width: 25px; height: 25px"></div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       } | ||||
|    | ||||
|     </div> | ||||
|   </ng-container> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| .fee-card { | ||||
|   padding: 15px; | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
| 
 | ||||
|   .feerate { | ||||
|     display: flex; | ||||
| @ -23,7 +23,7 @@ | ||||
| } | ||||
| 
 | ||||
| .feerate.active { | ||||
|   background-color: #105fb0 !important; | ||||
|   background-color: var(--primary) !important; | ||||
|   opacity: 1; | ||||
|   border: 1px solid #007fff !important; | ||||
| } | ||||
| @ -109,4 +109,61 @@ | ||||
| 
 | ||||
| .item { | ||||
|   white-space: initial; | ||||
| } | ||||
| 
 | ||||
| .cashapp-cta { | ||||
|   width: 100%; | ||||
|   height: 54px; | ||||
|   background: #653b9c; | ||||
|   position: relative; | ||||
|   bottom: initial; | ||||
|   top: initial; | ||||
|   border-radius: 3px; | ||||
|   font-size: 14px; | ||||
|   line-height: 16px; | ||||
|   text-align: center; | ||||
|   padding: 4px 6px; | ||||
|   cursor: pointer; | ||||
|   box-shadow: 0px 0px 15px 0px #000; | ||||
| 
 | ||||
|   &.sticky-top { | ||||
|     position: fixed; | ||||
|     width: calc(100vw - 30px - 1.5rem); | ||||
|     margin: auto; | ||||
|     z-index: 50; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     top: 102px; | ||||
|     @media (min-width: 573px) { | ||||
|       top: 62px; | ||||
|     } | ||||
|   } | ||||
|   &.sticky-bottom { | ||||
|     position: fixed; | ||||
|     width: calc(100vw - 30px - 1.5rem); | ||||
|     margin: auto; | ||||
|     z-index: 50; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     bottom: 50px; | ||||
|     @media (min-width: 430px) { | ||||
|       bottom: 56px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @media (max-width: 400px) { | ||||
|     width: calc(100% + 1.5rem); | ||||
|     margin: 0 -0.75rem; | ||||
|     &.sticky-top, &.sticky-bottom { | ||||
|       width: calc(100vw - 30px); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .cashapp-placeholder { | ||||
|   height: 54px; | ||||
| 
 | ||||
|   &.non-stick { | ||||
|     height: 0px; | ||||
|   } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; | ||||
| import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core'; | ||||
| import { Subscription, catchError, of, tap } from 'rxjs'; | ||||
| import { StorageService } from '../../services/storage.service'; | ||||
| import { Transaction } from '../../interfaces/electrs.interface'; | ||||
| @ -43,6 +43,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|   @Input() tx: Transaction | undefined; | ||||
|   @Input() scrollEvent: boolean; | ||||
| 
 | ||||
|   @ViewChild('cashappCTA') | ||||
|   cashappCTA: ElementRef; | ||||
| 
 | ||||
|   math = Math; | ||||
|   error = ''; | ||||
|   showSuccess = false; | ||||
| @ -56,9 +59,11 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|   defaultBid = 0; | ||||
|   maxCost = 0; | ||||
|   userBid = 0; | ||||
|   accelerationUUID: string; | ||||
|   selectFeeRateIndex = 1; | ||||
|   isMobile: boolean = window.innerWidth <= 767.98; | ||||
|   user: any = undefined; | ||||
|   stickyCTA: string = 'non-stick'; | ||||
| 
 | ||||
|   maxRateOptions: RateOption[] = []; | ||||
| 
 | ||||
| @ -66,6 +71,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|   paymentType: 'bitcoin' | 'cashapp' = 'bitcoin'; | ||||
|   cashAppSubscription: Subscription; | ||||
|   conversionsSubscription: Subscription; | ||||
|   cashappSubmit: any; | ||||
|   payments: any; | ||||
|   showSpinner = false; | ||||
|   square: any; | ||||
| @ -80,7 +86,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|     private cd: ChangeDetectorRef | ||||
|   ) { | ||||
|     if (this.stateService.ref === 'https://cash.app/') { | ||||
|       this.paymentType = 'cashapp'; | ||||
|       this.insertSquare(); | ||||
|     } else { | ||||
|       this.paymentType = 'bitcoin'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -94,21 +103,23 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|   } | ||||
| 
 | ||||
|   ngOnInit() { | ||||
|     this.accelerationUUID = window.crypto.randomUUID(); | ||||
|     if (this.stateService.ref === 'https://cash.app/') { | ||||
|       this.paymentType = 'cashapp'; | ||||
|       this.stateService.ref = ''; | ||||
|     } else { | ||||
|       this.paymentType = 'bitcoin'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ngOnChanges(changes: SimpleChanges): void { | ||||
|     if (changes.scrollEvent) { | ||||
|     if (changes.scrollEvent && this.paymentType !== 'cashapp' && this.stateService.ref !== 'https://cash.app/') { | ||||
|       this.scrollToPreview('acceleratePreviewAnchor', 'start'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ngAfterViewInit() { | ||||
|     this.onScroll(); | ||||
| 
 | ||||
|     if (this.paymentType === 'cashapp') { | ||||
|       this.showSpinner = true; | ||||
|     } | ||||
| @ -173,10 +184,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|             this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; | ||||
| 
 | ||||
|             if (!this.error) { | ||||
|               this.scrollToPreview('acceleratePreviewAnchor', 'start'); | ||||
|               if (this.paymentType === 'cashapp') { | ||||
|                 this.setupSquare(); | ||||
|               }  | ||||
|               } else { | ||||
|                 this.scrollToPreview('acceleratePreviewAnchor', 'start'); | ||||
|               } | ||||
| 
 | ||||
|               setTimeout(() => { | ||||
|                 this.onScroll(); | ||||
|               }, 100); | ||||
|             } | ||||
|           } | ||||
|         }), | ||||
| @ -231,7 +247,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|     } | ||||
|     this.accelerationSubscription = this.servicesApiService.accelerate$( | ||||
|       this.tx.txid, | ||||
|       this.userBid | ||||
|       this.userBid, | ||||
|       this.accelerationUUID | ||||
|     ).subscribe({ | ||||
|       next: () => { | ||||
|         this.audioService.playSound('ascend-chime-cartoon'); | ||||
| @ -301,6 +318,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|      | ||||
|     this.conversionsSubscription = this.stateService.conversions$.subscribe( | ||||
|       async (conversions) => { | ||||
|         if (this.cashAppPay) { | ||||
|           this.cashAppPay.destroy(); | ||||
|         } | ||||
| 
 | ||||
|         const maxCostUsd = this.maxCost / 100_000_000 * conversions.USD; | ||||
|         const paymentRequest = this.payments.paymentRequest({ | ||||
|           countryCode: 'US', | ||||
| @ -310,13 +331,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|             label: 'Total', | ||||
|             pending: true, | ||||
|             productUrl: `https://mempool.space/tx/${this.tx.txid}`, | ||||
|           } | ||||
|           }, | ||||
|           button: { shape: 'semiround', size: 'small', theme: 'light'} | ||||
|         }); | ||||
|         this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { | ||||
|           redirectURL: `https://mempool.space/tx/${this.tx.txid}`, | ||||
|           referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, | ||||
|           button: { shape: 'semiround', size: 'small', theme: 'light'} | ||||
|         }); | ||||
|         await this.cashAppPay.attach('#cash-app-pay'); | ||||
|         const renderPromise = this.cashAppPay.CashAppPayInstance.render('#cash-app-pay', { button: { theme: 'light', size: 'small', shape: 'semiround' }, manage: false }); | ||||
|         this.showSpinner = false; | ||||
|          | ||||
|         const that = this; | ||||
| @ -332,7 +355,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|               that.userBid, | ||||
|               tokenResult.token, | ||||
|               tokenResult.details.cashAppPay.cashtag, | ||||
|               tokenResult.details.cashAppPay.referenceId | ||||
|               tokenResult.details.cashAppPay.referenceId, | ||||
|               that.accelerationUUID | ||||
|             ).subscribe({ | ||||
|               next: () => { | ||||
|                 that.audioService.playSound('ascend-chime-cartoon'); | ||||
| @ -351,13 +375,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|             }); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         this.cashappSubmit = await renderPromise; | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   insertSquare(): void { | ||||
|     let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js'; | ||||
|     if (document.location.hostname === 'mempool-staging.tk7.mempool.space' || document.location.hostname === 'mempool.space') { | ||||
|     if (document.location.hostname === 'mempool-staging.fmt.mempool.space' || | ||||
|         document.location.hostname === 'mempool-staging.va1.mempool.space' || | ||||
|         document.location.hostname === 'mempool-staging.fra.mempool.space' || | ||||
|         document.location.hostname === 'mempool-staging.tk7.mempool.space' || | ||||
|         document.location.hostname === 'mempool.space') { | ||||
|       statsUrl = 'https://web.squarecdn.com/v1/square.js'; | ||||
|     } | ||||
| 
 | ||||
| @ -367,4 +397,34 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | ||||
|       g.type='text/javascript'; g.src=statsUrl; s.parentNode.insertBefore(g, s); | ||||
|     })(); | ||||
|   } | ||||
| 
 | ||||
|   submitCashappPay(): void { | ||||
|     if (this.cashappSubmit) { | ||||
|       this.cashappSubmit?.begin(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @HostListener('window:scroll', ['$event']) // for window scroll events
 | ||||
|   onScroll() { | ||||
|     if (this.estimate && !this.cashappCTA?.nativeElement) { | ||||
|       setTimeout(() => { | ||||
|         this.onScroll(); | ||||
|       }, 200); | ||||
|       return; | ||||
|     } | ||||
|     if (!this.cashappCTA?.nativeElement || this.paymentType !== 'cashapp' || !this.isMobile) { | ||||
|       return; | ||||
|     } | ||||
|     const cta = this.cashappCTA.nativeElement; | ||||
|     const rect = cta.getBoundingClientRect(); | ||||
|     const topOffset = window.innerWidth <= 572 ? 102 : 62; | ||||
|     const bottomOffset = window.innerWidth < 430 ? 50 : 56; | ||||
|     if (rect.top < topOffset) { | ||||
|       this.stickyCTA = 'sticky-top'; | ||||
|     } else if (rect.top > window.innerHeight - (bottomOffset + 54)) { | ||||
|       this.stickyCTA = 'sticky-bottom'; | ||||
|     } else { | ||||
|       this.stickyCTA = 'non-stick'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -45,7 +45,7 @@ | ||||
|     </form> | ||||
|   </div> | ||||
| 
 | ||||
|   <div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" | ||||
|   <div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions" | ||||
|     (chartInit)="onChartInit($event)"> | ||||
|   </div> | ||||
|   <div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading"> | ||||
|  | ||||
| @ -62,7 +62,7 @@ h5 { | ||||
| 
 | ||||
| .card-title { | ||||
|   font-size: 1rem; | ||||
|   color: #4a68b9; | ||||
|   color: var(--title-fg); | ||||
| } | ||||
| 
 | ||||
| .disabled { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| .card-title { | ||||
|   color: #4a68b9; | ||||
|   color: var(--title-fg); | ||||
|   font-size: 10px; | ||||
|   margin-bottom: 4px;   | ||||
|   font-size: 1rem; | ||||
|  | ||||
| @ -39,10 +39,10 @@ | ||||
|             </td> | ||||
|           </ng-container> | ||||
|           <ng-container *ngIf="!pending"> | ||||
|             <td *ngIf="acceleration.feePaid" class="fee text-right"> | ||||
|               {{ (acceleration.boost) | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> | ||||
|             <td *ngIf="acceleration.boost != null" class="fee text-right"> | ||||
|               {{ acceleration.boost | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> | ||||
|             </td> | ||||
|             <td *ngIf="!acceleration.feePaid" class="fee text-right"> | ||||
|             <td *ngIf="acceleration.boost == null" class="fee text-right"> | ||||
|               ~ | ||||
|             </td> | ||||
|             <td class="block text-right"> | ||||
|  | ||||
| @ -59,7 +59,7 @@ tr, td, th { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .txid { | ||||
| @ -148,7 +148,7 @@ tr, td, th { | ||||
| 
 | ||||
| .tooltip-custom .tooltiptext { | ||||
|   visibility: hidden; | ||||
|   color: #fff; | ||||
|   color: var(--fg); | ||||
|   text-align: center; | ||||
|   padding: 5px 0; | ||||
|   border-radius: 6px; | ||||
|  | ||||
| @ -58,7 +58,7 @@ export class AccelerationsListComponent implements OnInit { | ||||
|               } | ||||
|             } | ||||
|             for (const acc of accelerations) { | ||||
|               acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee; | ||||
|               acc.boost = acc.boostCost != null ? acc.boostCost : (acc.feePaid - acc.baseFee - acc.vsizeFee); | ||||
|             } | ||||
|             if (this.widget) { | ||||
|               return of(accelerations.slice(0, 6)); | ||||
|  | ||||
| @ -40,7 +40,7 @@ | ||||
|           <a class="title-link" href="" [routerLink]="['/mempool-block/0' | relativeUrl]"> | ||||
|             <h5 class="card-title d-inline">Mempool Goggles™ : <ng-container i18n="accelerator.accelerations">Accelerations</ng-container></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> | ||||
|           <div class="mempool-block-wrapper" *ngIf="webGlEnabled"> | ||||
|             <app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview> | ||||
| @ -85,7 +85,7 @@ | ||||
|           <a class="title-link" href="" [routerLink]="['/acceleration/list' | relativeUrl]"> | ||||
|             <h5 class="card-title d-inline" i18n="dashboard.recent-accelerations">Recent Accelerations</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-accelerations-list [attr.data-cy]="'recent-accelerations'" [widget]=true [accelerations$]="minedAccelerations$"></app-accelerations-list> | ||||
|         </div> | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| } | ||||
| 
 | ||||
| .card { | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
| } | ||||
| 
 | ||||
| .graph-card { | ||||
| @ -29,10 +29,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 { | ||||
|  | ||||
| @ -8,13 +8,15 @@ import { Observable, catchError, combineLatest, distinctUntilChanged, interval, | ||||
| import { Color } from '../../block-overview-graph/sprite-types'; | ||||
| import { hexToColor } from '../../block-overview-graph/utils'; | ||||
| import TxView from '../../block-overview-graph/tx-view'; | ||||
| import { feeLevels, mempoolFeeColors } from '../../../app.constants'; | ||||
| import { feeLevels, defaultMempoolFeeColors, contrastMempoolFeeColors } from '../../../app.constants'; | ||||
| import { ServicesApiServices } from '../../../services/services-api.service'; | ||||
| import { detectWebGL } from '../../../shared/graphs.utils'; | ||||
| import { AudioService } from '../../../services/audio.service'; | ||||
| import { ThemeService } from '../../../services/theme.service'; | ||||
| 
 | ||||
| const acceleratedColor: Color = hexToColor('8F5FF6'); | ||||
| const normalColors = mempoolFeeColors.map(hex => hexToColor(hex.slice(0,6) + '5F')); | ||||
| const normalColors = defaultMempoolFeeColors.map(hex => hexToColor(hex + '5F')); | ||||
| const contrastColors = contrastMempoolFeeColors.map(hex => hexToColor(hex.slice(0,6) + '5F')); | ||||
| 
 | ||||
| interface AccelerationBlock extends BlockExtended { | ||||
|   accelerationCount: number, | ||||
| @ -37,6 +39,7 @@ export class AcceleratorDashboardComponent implements OnInit { | ||||
|   firstLoad = true; | ||||
| 
 | ||||
|   graphHeight: number = 300; | ||||
|   theme: ThemeService; | ||||
| 
 | ||||
|   constructor( | ||||
|     private seoService: SeoService, | ||||
| @ -116,15 +119,15 @@ export class AcceleratorDashboardComponent implements OnInit { | ||||
|       switchMap(([accelerations, blocks]) => { | ||||
|         const blockMap = {}; | ||||
|         for (const block of blocks) { | ||||
|           blockMap[block.id] = block; | ||||
|           blockMap[block.height] = block; | ||||
|         } | ||||
|         const accelerationsByBlock: { [ hash: string ]: Acceleration[] } = {}; | ||||
|         const accelerationsByBlock: { [ height: number ]: Acceleration[] } = {}; | ||||
|         for (const acceleration of accelerations) { | ||||
|           if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHash]?.extras.pool.id)) { | ||||
|             if (!accelerationsByBlock[acceleration.blockHash]) { | ||||
|               accelerationsByBlock[acceleration.blockHash] = []; | ||||
|           if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHeight]?.extras.pool.id)) { | ||||
|             if (!accelerationsByBlock[acceleration.blockHeight]) { | ||||
|               accelerationsByBlock[acceleration.blockHeight] = []; | ||||
|             } | ||||
|             accelerationsByBlock[acceleration.blockHash].push(acceleration); | ||||
|             accelerationsByBlock[acceleration.blockHeight].push(acceleration); | ||||
|           } | ||||
|         } | ||||
|         return of(blocks.slice(0, 6).map(block => { | ||||
| @ -141,7 +144,7 @@ export class AcceleratorDashboardComponent implements OnInit { | ||||
|     } else { | ||||
|       const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
 | ||||
|       const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1; | ||||
|       return normalColors[feeLevelIndex] || normalColors[mempoolFeeColors.length - 1]; | ||||
|       return this.theme.theme === 'contrast' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1]; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| .card-title { | ||||
|   color: #4a68b9; | ||||
|   color: var(--title-fg); | ||||
|   font-size: 10px; | ||||
|   margin-bottom: 4px;   | ||||
|   font-size: 1rem; | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { ChainStats } from '../../interfaces/electrs.interface'; | ||||
| import { ElectrsApiService } from '../../services/electrs-api.service'; | ||||
| import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-address-graph', | ||||
| @ -46,6 +47,7 @@ export class AddressGraphComponent implements OnChanges { | ||||
|     private router: Router, | ||||
|     private amountShortenerPipe: AmountShortenerPipe, | ||||
|     private cd: ChangeDetectorRef, | ||||
|     private relativeUrlPipe: RelativeUrlPipe, | ||||
|   ) {} | ||||
| 
 | ||||
|   ngOnChanges(changes: SimpleChanges): void { | ||||
| @ -122,7 +124,7 @@ export class AddressGraphComponent implements OnChanges { | ||||
|               </div> | ||||
|               <span>${date}</span> | ||||
|             </div> | ||||
|           `; 
 | ||||
|           `;
 | ||||
|         }.bind(this) | ||||
|       }, | ||||
|       xAxis: { | ||||
| @ -159,7 +161,7 @@ export class AddressGraphComponent implements OnChanges { | ||||
|       ], | ||||
|       series: [ | ||||
|         { | ||||
|           name: $localize`Balance:Balance`, | ||||
|           name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`, | ||||
|           showSymbol: false, | ||||
|           symbol: 'circle', | ||||
|           symbolSize: 8, | ||||
| @ -178,7 +180,7 @@ export class AddressGraphComponent implements OnChanges { | ||||
| 
 | ||||
|   onChartClick(e) { | ||||
|     if (this.hoverData?.length && this.hoverData[0]?.[2]?.txid) { | ||||
|       this.router.navigate(['/tx/', this.hoverData[0][2].txid]); | ||||
|       this.router.navigate([this.relativeUrlPipe.transform('/tx/'), this.hoverData[0][2].txid]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| } | ||||
| 
 | ||||
| .qr-wrapper { | ||||
|   background-color: #FFF; | ||||
|   background-color: var(--fg); | ||||
|   padding: 10px; | ||||
|   padding-bottom: 5px; | ||||
|   display: inline-block; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| .qr-wrapper { | ||||
|   background-color: #FFF; | ||||
|   background-color: var(--fg); | ||||
|   padding: 10px; | ||||
|   padding-bottom: 5px; | ||||
|   display: inline-block; | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| .green-color { | ||||
|   color: #3bcc49; | ||||
|   color: var(--green); | ||||
| } | ||||
| @ -1,5 +1,5 @@ | ||||
| .qr-wrapper { | ||||
|   background-color: #FFF; | ||||
|   background-color: var(--fg); | ||||
|   padding: 10px; | ||||
|   padding-bottom: 5px; | ||||
|   display: inline-block; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -95,7 +95,7 @@ | ||||
|     } | ||||
|     .card-title { | ||||
|       font-size: 1rem; | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|     } | ||||
|     .card-text { | ||||
|       font-size: 18px; | ||||
|  | ||||
| @ -233,7 +233,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', | ||||
| @ -309,7 +309,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { | ||||
|         splitLine: { | ||||
|           lineStyle: { | ||||
|             type: 'dotted', | ||||
|             color: '#ffffff66', | ||||
|             color: 'var(--transparent-fg)', | ||||
|             opacity: 0.25, | ||||
|           } | ||||
|         }, | ||||
| @ -376,7 +376,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, | ||||
|  | ||||
| @ -151,7 +151,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', | ||||
| @ -214,7 +214,7 @@ export class BlockFeesGraphComponent implements OnInit { | ||||
|           splitLine: { | ||||
|             lineStyle: { | ||||
|               type: 'dotted', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 0.25, | ||||
|             } | ||||
|           }, | ||||
| @ -305,7 +305,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, | ||||
|  | ||||
| @ -71,7 +71,7 @@ | ||||
|   .filter-tag { | ||||
|     font-size: 0.9em; | ||||
|     background: #181b2daf; | ||||
|     border: solid 1px #105fb0; | ||||
|     border: solid 1px var(--primary); | ||||
|     color: white; | ||||
|     border-radius: 0.2rem; | ||||
|     padding: 0.2em 0.5em; | ||||
| @ -80,15 +80,15 @@ | ||||
|     pointer-events: all; | ||||
| 
 | ||||
|     &.selected { | ||||
|       background-color: #105fb0; | ||||
|       background-color: var(--primary); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.any-mode { | ||||
|     .filter-tag { | ||||
|       border: solid 1px #1a9436; | ||||
|       border: solid 1px var(--success); | ||||
|       &.selected { | ||||
|         background-color: #1a9436; | ||||
|         background-color: var(--success); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @ -114,15 +114,15 @@ | ||||
|     } | ||||
| 
 | ||||
|     &.blue { | ||||
|       border: solid 1px #105fb0; | ||||
|       border: solid 1px var(--primary); | ||||
|       &.active { | ||||
|         background: #105fb0; | ||||
|         background: var(--primary); | ||||
|       } | ||||
|     } | ||||
|     &.green { | ||||
|       border: solid 1px #1a9436; | ||||
|       border: solid 1px var(--success); | ||||
|       &.active { | ||||
|         background: #1a9436; | ||||
|         background: var(--success); | ||||
|       } | ||||
|     } | ||||
|     &.yellow { | ||||
|  | ||||
| @ -131,7 +131,7 @@ export class BlockHealthGraphComponent implements OnInit { | ||||
|         borderRadius: 4, | ||||
|         shadowColor: 'rgba(0, 0, 0, 0.5)', | ||||
|         textStyle: { | ||||
|           color: '#b1b1b1', | ||||
|           color: 'var(--tooltip-grey)', | ||||
|           align: 'left', | ||||
|         }, | ||||
|         borderColor: '#000', | ||||
| @ -178,7 +178,7 @@ export class BlockHealthGraphComponent implements OnInit { | ||||
|           splitLine: { | ||||
|             lineStyle: { | ||||
|               type: 'dotted', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 0.25, | ||||
|             } | ||||
|           }, | ||||
| @ -290,7 +290,7 @@ export class BlockHealthGraphComponent 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; | ||||
|  | ||||
| @ -7,8 +7,9 @@ import TxView from './tx-view'; | ||||
| import { Color, Position } from './sprite-types'; | ||||
| import { Price } from '../../services/price.service'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { ThemeService } from '../../services/theme.service'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction } from './utils'; | ||||
| import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction, contrastColorFunction, contrastAuditColors, contrastColors } from './utils'; | ||||
| import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; | ||||
| import { detectWebGL } from '../../shared/graphs.utils'; | ||||
| 
 | ||||
| @ -20,6 +21,13 @@ const unmatchedAuditColors = { | ||||
|   prioritized: setOpacity(defaultAuditColors.prioritized, unmatchedOpacity), | ||||
|   accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity), | ||||
| }; | ||||
| const unmatchedContrastAuditColors = { | ||||
|   censored: setOpacity(contrastAuditColors.censored, unmatchedOpacity), | ||||
|   missing: setOpacity(contrastAuditColors.missing, unmatchedOpacity), | ||||
|   added: setOpacity(contrastAuditColors.added, unmatchedOpacity), | ||||
|   prioritized: setOpacity(contrastAuditColors.prioritized, unmatchedOpacity), | ||||
|   accelerated: setOpacity(contrastAuditColors.accelerated, unmatchedOpacity), | ||||
| }; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-block-overview-graph', | ||||
| @ -53,6 +61,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
| 
 | ||||
|   @ViewChild('blockCanvas') | ||||
|   canvas: ElementRef<HTMLCanvasElement>; | ||||
|   themeChangedSubscription: Subscription; | ||||
| 
 | ||||
|   gl: WebGLRenderingContext; | ||||
|   animationFrameRequest: number; | ||||
| @ -84,6 +93,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     readonly ngZone: NgZone, | ||||
|     readonly elRef: ElementRef, | ||||
|     public stateService: StateService, | ||||
|     private themeService: ThemeService, | ||||
|   ) { | ||||
|     this.webGlEnabled = this.stateService.isBrowser && detectWebGL(); | ||||
|     this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); | ||||
| @ -102,6 +112,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|       if (this.gl) { | ||||
|         this.initCanvas(); | ||||
|         this.resizeCanvas(); | ||||
|         this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => { | ||||
|           this.scene.setColorFunction(this.getColorFunction()); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @ -148,6 +161,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     if (this.canvas) { | ||||
|       this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); | ||||
|       this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); | ||||
|       this.themeChangedSubscription?.unsubscribe(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -293,7 +307,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, | ||||
|           highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset, | ||||
|         colorFunction: this.getColorFunction() }); | ||||
|         this.start(); | ||||
| @ -563,14 +577,27 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|   getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) { | ||||
|     return (tx: TxView) => { | ||||
|       if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) { | ||||
|         return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)); | ||||
|         if (this.themeService.theme !== 'contrast') { | ||||
|           return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)); | ||||
|         } else { | ||||
|           return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)); | ||||
|         } | ||||
|       } else { | ||||
|         return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction( | ||||
|           tx, | ||||
|           defaultColors.unmatchedfee, | ||||
|           unmatchedAuditColors, | ||||
|           this.relativeTime || (Date.now() / 1000) | ||||
|         ); | ||||
|         if (this.themeService.theme !== 'contrast') { | ||||
|           return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction( | ||||
|             tx, | ||||
|             defaultColors.unmatchedfee, | ||||
|             unmatchedAuditColors, | ||||
|             this.relativeTime || (Date.now() / 1000) | ||||
|           ); | ||||
|         } else { | ||||
|           return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : contrastColorFunction( | ||||
|             tx, | ||||
|             contrastColors.unmatchedfee, | ||||
|             unmatchedContrastAuditColors, | ||||
|             this.relativeTime || (Date.now() / 1000) | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
| @ -2,13 +2,15 @@ import { FastVertexArray } from './fast-vertex-array'; | ||||
| import TxView from './tx-view'; | ||||
| import { TransactionStripped } from '../../interfaces/node-api.interface'; | ||||
| import { Color, Position, Square, ViewUpdateParams } from './sprite-types'; | ||||
| import { defaultColorFunction } from './utils'; | ||||
| import { defaultColorFunction, contrastColorFunction } from './utils'; | ||||
| import { ThemeService } from '../../services/theme.service'; | ||||
| 
 | ||||
| export default class BlockScene { | ||||
|   scene: { count: number, offset: { x: number, y: number}}; | ||||
|   vertexArray: FastVertexArray; | ||||
|   txs: { [key: string]: TxView }; | ||||
|   getColor: ((tx: TxView) => Color) = defaultColorFunction; | ||||
|   theme: ThemeService; | ||||
|   orientation: string; | ||||
|   flip: boolean; | ||||
|   animationDuration: number = 900; | ||||
| @ -29,11 +31,11 @@ export default class BlockScene { | ||||
|   animateUntil = 0; | ||||
|   dirty: boolean; | ||||
| 
 | ||||
|   constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }: | ||||
|   constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }: | ||||
|       { width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number, | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|   ) { | ||||
|     this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }); | ||||
|     this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }); | ||||
|   } | ||||
| 
 | ||||
|   resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void { | ||||
| @ -67,7 +69,7 @@ export default class BlockScene { | ||||
|   } | ||||
| 
 | ||||
|   setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void { | ||||
|     this.getColor = colorFunction || defaultColorFunction; | ||||
|     this.theme.theme !== 'default' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction; | ||||
|     this.updateAllColors(); | ||||
|   } | ||||
| 
 | ||||
| @ -197,6 +199,7 @@ export default class BlockScene { | ||||
|           this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize); | ||||
|           this.txs[tx.txid].rate = tx.rate; | ||||
|           this.txs[tx.txid].dirty = true; | ||||
|           this.updateColor(this.txs[tx.txid], startTime, 50, true); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
| @ -232,9 +235,9 @@ export default class BlockScene { | ||||
|     this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value)); | ||||
|   } | ||||
| 
 | ||||
|   private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }: | ||||
|   private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }: | ||||
|       { width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number, | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|   ): void { | ||||
|     this.animationDuration = animationDuration || 1000; | ||||
|     this.configAnimationOffset = animationOffset; | ||||
| @ -243,7 +246,8 @@ export default class BlockScene { | ||||
|     this.flip = flip; | ||||
|     this.vertexArray = vertexArray; | ||||
|     this.highlightingEnabled = highlighting; | ||||
|     this.getColor = colorFunction || defaultColorFunction; | ||||
|     theme.theme !== 'default' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction; | ||||
|     this.theme = theme; | ||||
| 
 | ||||
|     this.scene = { | ||||
|       count: 0, | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { feeLevels, mempoolFeeColors } from '../../app.constants'; | ||||
| import { feeLevels, defaultMempoolFeeColors, contrastMempoolFeeColors } from '../../app.constants'; | ||||
| import { Color } from './sprite-types'; | ||||
| import TxView from './tx-view'; | ||||
| 
 | ||||
| @ -47,7 +47,7 @@ interface ColorPalette { | ||||
| // precomputed colors
 | ||||
| const defaultColors: { [key: string]: ColorPalette } = { | ||||
|   fee: { | ||||
|     base: mempoolFeeColors.map(hexToColor), | ||||
|     base: defaultMempoolFeeColors.map(hexToColor), | ||||
|     audit: [], | ||||
|     marginal: [], | ||||
|     baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1 | ||||
| @ -72,7 +72,37 @@ export const defaultAuditColors = { | ||||
|   missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), | ||||
|   added: hexToColor('0099ff'), | ||||
|   prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), | ||||
|   accelerated: hexToColor('8F5FF6'), | ||||
|   accelerated: hexToColor('8f5ff6'), | ||||
| }; | ||||
| 
 | ||||
| const contrastColors: { [key: string]: ColorPalette } = { | ||||
|   fee: { | ||||
|     base: contrastMempoolFeeColors.map(hexToColor), | ||||
|     audit: [], | ||||
|     marginal: [], | ||||
|     baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1 | ||||
|   }, | ||||
| } | ||||
| for (const key in contrastColors) { | ||||
|   const base = contrastColors[key].base; | ||||
|   contrastColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9)); | ||||
|   contrastColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1)); | ||||
|   contrastColors['unmatched' + key] = { | ||||
|     base: contrastColors[key].base.map(c => setOpacity(c, 0.2)), | ||||
|     audit: contrastColors[key].audit.map(c => setOpacity(c, 0.2)), | ||||
|     marginal: contrastColors[key].marginal.map(c => setOpacity(c, 0.2)), | ||||
|     baseLevel: contrastColors[key].baseLevel, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export { contrastColors as contrastColors }; | ||||
| 
 | ||||
| export const contrastAuditColors = { | ||||
|   censored: hexToColor('ffa8ff'), | ||||
|   missing: darken(desaturate(hexToColor('ffa8ff'), 0.3), 0.7), | ||||
|   added: hexToColor('00bb98'), | ||||
|   prioritized: darken(desaturate(hexToColor('00bb98'), 0.3), 0.7), | ||||
|   accelerated: hexToColor('8f5ff6'), | ||||
| }; | ||||
| 
 | ||||
| export function defaultColorFunction( | ||||
| @ -83,7 +113,7 @@ export function defaultColorFunction( | ||||
| ): Color { | ||||
|   const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
 | ||||
|   const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000)); | ||||
|   const levelColor = colors.base[levelIndex] || colors.base[mempoolFeeColors.length - 1]; | ||||
|   const levelColor = colors.base[levelIndex] || colors.base[defaultMempoolFeeColors.length - 1]; | ||||
|   // Normal mode
 | ||||
|   if (!tx.scene?.highlightingEnabled) { | ||||
|     if (tx.acc) { | ||||
| @ -100,7 +130,7 @@ export function defaultColorFunction( | ||||
|     case 'missing': | ||||
|     case 'sigop': | ||||
|     case 'rbf': | ||||
|       return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; | ||||
|       return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1]; | ||||
|     case 'fresh': | ||||
|     case 'freshcpfp': | ||||
|       return auditColors.missing; | ||||
| @ -109,12 +139,12 @@ export function defaultColorFunction( | ||||
|     case 'prioritized': | ||||
|       return auditColors.prioritized; | ||||
|     case 'selected': | ||||
|       return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; | ||||
|       return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1]; | ||||
|     case 'accelerated': | ||||
|       return auditColors.accelerated; | ||||
|     case 'found': | ||||
|       if (tx.context === 'projected') { | ||||
|         return colors.audit[levelIndex] || colors.audit[mempoolFeeColors.length - 1]; | ||||
|         return colors.audit[levelIndex] || colors.audit[defaultMempoolFeeColors.length - 1]; | ||||
|       } else { | ||||
|         return levelColor; | ||||
|       } | ||||
| @ -127,17 +157,27 @@ export function defaultColorFunction( | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function contrastColorFunction( | ||||
|   tx: TxView, | ||||
|   colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = contrastColors.fee, | ||||
|   auditColors: { [status: string]: Color } = contrastAuditColors, | ||||
|   relativeTime?: number, | ||||
| ): Color { | ||||
|   return defaultColorFunction(tx, colors, auditColors, relativeTime); | ||||
| } | ||||
| 
 | ||||
| export function ageColorFunction( | ||||
|   tx: TxView, | ||||
|   colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee, | ||||
|   auditColors: { [status: string]: Color } = defaultAuditColors, | ||||
|   relativeTime?: number, | ||||
|   theme?: string, | ||||
| ): Color { | ||||
|   if (tx.acc || tx.status === 'accelerated') { | ||||
|     return auditColors.accelerated; | ||||
|   } | ||||
| 
 | ||||
|   const color = defaultColorFunction(tx, colors, auditColors, relativeTime);  | ||||
|   const color = theme !== 'contrast' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime); | ||||
| 
 | ||||
|   const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60))))))); | ||||
|   return { | ||||
| @ -146,4 +186,4 @@ export function ageColorFunction( | ||||
|     b: color.b, | ||||
|     a: color.a * (1 - ageLevel) | ||||
|   }; | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| @ -30,7 +30,7 @@ th, td { | ||||
| } | ||||
| 
 | ||||
| .badge.badge-accelerated { | ||||
|   background-color: #653b9c; | ||||
|   background-color: var(--tertiary); | ||||
|   box-shadow: #ad7de57f 0px 0px 12px -2px; | ||||
|   color: white; | ||||
|   animation: acceleratePulse 1s infinite; | ||||
| @ -51,27 +51,27 @@ th, td { | ||||
| 
 | ||||
|   .filter-tag { | ||||
|     background: #181b2daf; | ||||
|     border: solid 1px #105fb0; | ||||
|     border: solid 1px var(--primary); | ||||
|     color: white; | ||||
|     transition: background-color 300ms; | ||||
| 
 | ||||
|     &.matching { | ||||
|       background-color: #105fb0; | ||||
|       background-color: var(--primary); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.any-mode { | ||||
|     .filter-tag { | ||||
|       border: solid 1px #1a9436; | ||||
|       border: solid 1px var(--success); | ||||
|       &.matching { | ||||
|         background-color: #1a9436; | ||||
|         background-color: var(--success); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes acceleratePulse { | ||||
|   0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; } | ||||
|   0% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; } | ||||
|   50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;} | ||||
|   100% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; } | ||||
|   100% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; } | ||||
| } | ||||
| @ -150,7 +150,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', | ||||
| @ -219,7 +219,7 @@ export class BlockRewardsGraphComponent implements OnInit { | ||||
|           splitLine: { | ||||
|             lineStyle: { | ||||
|               type: 'dotted', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 0.25, | ||||
|             } | ||||
|           }, | ||||
| @ -315,7 +315,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, | ||||
|  | ||||
| @ -146,7 +146,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', | ||||
| @ -230,7 +230,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | ||||
|           splitLine: { | ||||
|             lineStyle: { | ||||
|               type: 'dotted', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 0.25, | ||||
|             } | ||||
|           }, | ||||
| @ -252,7 +252,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | ||||
|             symbol: 'none', | ||||
|             lineStyle: { | ||||
|               type: 'solid', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 1, | ||||
|               width: 1, | ||||
|             }, | ||||
| @ -342,7 +342,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, | ||||
|  | ||||
| @ -136,7 +136,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { | ||||
|                   return of(transactions); | ||||
|                 }) | ||||
|               ), | ||||
|             this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) | ||||
|             this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([]) | ||||
|           ]); | ||||
|         } | ||||
|       ), | ||||
|  | ||||
| @ -22,7 +22,7 @@ | ||||
| } | ||||
| 
 | ||||
| .qr-wrapper { | ||||
|   background-color: #FFF; | ||||
|   background-color: var(--fg); | ||||
|   padding: 10px; | ||||
|   padding-bottom: 5px; | ||||
|   display: inline-block; | ||||
| @ -175,9 +175,7 @@ h1 { | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: #1ad8f4; | ||||
|     &:hover, &:focus { | ||||
|       color: #09a3ba; | ||||
|       display: inline-block; | ||||
|     } | ||||
|   } | ||||
| @ -254,7 +252,7 @@ h1 { | ||||
|   cursor: pointer; | ||||
| 
 | ||||
|   &.active { | ||||
|     background: #24273e; | ||||
|     background: var(--box-bg); | ||||
|   } | ||||
| 
 | ||||
|   &.active, &:hover { | ||||
|  | ||||
| @ -345,7 +345,7 @@ export class BlockComponent implements OnInit, OnDestroy { | ||||
|                 return of(null); | ||||
|               }) | ||||
|             ), | ||||
|           this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) | ||||
|           this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([]) | ||||
|         ]); | ||||
|       }) | ||||
|     ) | ||||
| @ -358,11 +358,15 @@ export class BlockComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|       const acceleratedInBlock = {}; | ||||
|       for (const acc of accelerations) { | ||||
|         acceleratedInBlock[acc.txid] = acc; | ||||
|         if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id || pool?.['pool_unique_id'] === this.block?.extras?.pool.id)) { | ||||
|           acceleratedInBlock[acc.txid] = acc; | ||||
|         } | ||||
|       } | ||||
|       for (const tx of transactions) { | ||||
|         if (acceleratedInBlock[tx.txid]) { | ||||
|           tx.acc = true; | ||||
|         } else { | ||||
|           tx.acc = false; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -117,7 +117,7 @@ | ||||
| } | ||||
| 
 | ||||
| .black-background { | ||||
|   background-color: #11131f; | ||||
|   background-color: var(--active-bg); | ||||
|   z-index: 100; | ||||
|   position: relative; | ||||
| } | ||||
| @ -144,7 +144,7 @@ | ||||
| } | ||||
| 
 | ||||
| .loading .bitcoin-block.mined-block { | ||||
|   background: #2d3348; | ||||
|   background: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| @keyframes opacityPulse { | ||||
|  | ||||
| @ -63,11 +63,11 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   blockPadding: number = 30; | ||||
| 
 | ||||
|   gradientColors = { | ||||
|     '': ['#9339f4', '#105fb0'], | ||||
|     liquid: ['#116761', '#183550'], | ||||
|     'liquidtestnet': ['#494a4a', '#272e46'], | ||||
|     testnet: ['#1d486f', '#183550'], | ||||
|     signet: ['#6f1d5d', '#471850'], | ||||
|     '': ['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( | ||||
| @ -330,7 +330,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
|       left: addLeft + this.blockOffset * index + '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% | ||||
|       )`,
 | ||||
| @ -366,7 +366,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
| 
 | ||||
|     return { | ||||
|       left: addLeft + this.blockOffset * this.emptyBlocks.indexOf(block) + 'px', | ||||
|       background: "#2d3348", | ||||
|       background: "var(--secondary)", | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,7 @@ | ||||
| } | ||||
| 
 | ||||
| .black-background { | ||||
|   background-color: #11131f; | ||||
|   background-color: var(--active-bg); | ||||
|   z-index: 100; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| @ -46,7 +46,7 @@ tr, td, th { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .pool { | ||||
| @ -266,7 +266,7 @@ tr, td, th { | ||||
| 
 | ||||
| .tooltip-custom .tooltiptext { | ||||
|   visibility: hidden; | ||||
|   color: #fff; | ||||
|   color: var(--fg); | ||||
|   text-align: center; | ||||
|   padding: 5px 0; | ||||
|   border-radius: 6px; | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     height: 100%; | ||||
| 
 | ||||
|     .face { | ||||
|       fill: #11131f; | ||||
|       fill: var(--active-bg); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -29,8 +29,8 @@ | ||||
|     } | ||||
| 
 | ||||
|     &.hour { | ||||
|       fill: #105fb0; | ||||
|       stroke: #105fb0; | ||||
|       fill: var(--primary); | ||||
|       stroke: var(--primary); | ||||
|       stroke-width: 6px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -161,7 +161,7 @@ | ||||
|     } | ||||
| 
 | ||||
|     .side.bottom { | ||||
|       background: #105fb0; | ||||
|       background: var(--primary); | ||||
|       transform: rotateX(-90deg);  | ||||
|       margin-top: var(--half-side); | ||||
|     } | ||||
|  | ||||
| @ -40,7 +40,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"> | ||||
|  | ||||
| @ -79,12 +79,12 @@ | ||||
| } | ||||
| 
 | ||||
| .card { | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| .card-title { | ||||
|   color: #4a68b9; | ||||
|   color: var(--title-fg); | ||||
|   font-size: 1rem; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
| @ -94,7 +94,7 @@ | ||||
| .progress { | ||||
|   display: inline-flex; | ||||
|   width: 100%; | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
|   height: 1.1rem; | ||||
|   max-width: 180px; | ||||
| } | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
|   justify-content: space-around; | ||||
|   height: 50.5px; | ||||
|   .shared-block { | ||||
|     color: #ffffff66; | ||||
|     color: var(--transparent-fg); | ||||
|     font-size: 12px; | ||||
|   } | ||||
|   .item { | ||||
| @ -91,19 +91,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; | ||||
| } | ||||
| @ -177,10 +177,10 @@ | ||||
| .epoch-blocks { | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   background: #2d3348; | ||||
|   background: var(--secondary); | ||||
| 
 | ||||
|   .rect { | ||||
|     fill: #2d3348; | ||||
|     fill: var(--secondary); | ||||
| 
 | ||||
|     &.behind { | ||||
|       fill: #D81B60; | ||||
| @ -189,7 +189,7 @@ | ||||
|       fill: url(#diff-gradient); | ||||
|     } | ||||
|     &.ahead { | ||||
|       fill: #1a9436; | ||||
|       fill: var(--success); | ||||
|     } | ||||
| 
 | ||||
|     &.hover { | ||||
| @ -223,12 +223,12 @@ | ||||
|     height: 100%; | ||||
|   } | ||||
|   .background { | ||||
|     background: linear-gradient(to right, #105fb0, #9339f4); | ||||
|     background: linear-gradient(to right, var(--primary), #9339f4); | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|   } | ||||
|   .remaining { | ||||
|     background: #2d3348; | ||||
|     background: var(--secondary); | ||||
|     right: 0; | ||||
|   } | ||||
|   .label { | ||||
|  | ||||
| @ -82,24 +82,24 @@ export class DifficultyComponent implements OnInit { | ||||
|     .pipe( | ||||
|       map(([blocks, da]) => { | ||||
|         const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), 0); | ||||
|         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 - (maxHeight % 210000); | ||||
|  | ||||
| @ -128,7 +128,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr | ||||
|         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, combineLatest } from 'rxjs'; | ||||
| import { Observable, combineLatest, Subscription } from 'rxjs'; | ||||
| import { Recommendedfees } from '../../interfaces/websocket.interface'; | ||||
| import { feeLevels, mempoolFeeColors } from '../../app.constants'; | ||||
| import { feeLevels } from '../../app.constants'; | ||||
| import { map, startWith, tap } from 'rxjs/operators'; | ||||
| import { ThemeService } from '../../services/theme.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-fees-box', | ||||
| @ -11,14 +12,18 @@ import { map, startWith, tap } from 'rxjs/operators'; | ||||
|   styleUrls: ['./fees-box.component.scss'], | ||||
|   changeDetection: ChangeDetectionStrategy.OnPush, | ||||
| }) | ||||
| export class FeesBoxComponent implements OnInit { | ||||
| export class FeesBoxComponent implements OnInit, OnDestroy { | ||||
|   isLoading$: 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 { | ||||
| @ -31,18 +36,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; | ||||
| 
 | ||||
| @ -40,16 +40,8 @@ | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .txPerSecond { | ||||
|   color: #4a9ff4; | ||||
| } | ||||
| 
 | ||||
| .mempoolSize { | ||||
|   color: #4a68b9; | ||||
| } | ||||
| 
 | ||||
| .unconfirmedTx { | ||||
|   color: #f14d80; | ||||
|   color: var(--title-fg); | ||||
| } | ||||
| 
 | ||||
| .info-block { | ||||
| @ -61,7 +53,7 @@ | ||||
| .progress { | ||||
|   display: inline-flex; | ||||
|   width: 160px; | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
|   height: 1.1rem; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -94,12 +94,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; | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -242,7 +242,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', | ||||
| @ -354,7 +354,7 @@ export class HashrateChartComponent implements OnInit { | ||||
|           splitLine: { | ||||
|             lineStyle: { | ||||
|               type: 'dotted', | ||||
|               color: '#ffffff66', | ||||
|               color: 'var(--transparent-fg)', | ||||
|               opacity: 0.25, | ||||
|             } | ||||
|           }, | ||||
| @ -472,7 +472,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, | ||||
|  | ||||
| @ -225,7 +225,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', | ||||
| @ -308,7 +308,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, | ||||
|  | ||||
| @ -272,7 +272,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On | ||||
|         splitLine: { | ||||
|           lineStyle: { | ||||
|             type: 'dotted', | ||||
|             color: '#ffffff66', | ||||
|             color: 'var(--transparent-fg)', | ||||
|             opacity: 0.25, | ||||
|           } | ||||
|         } | ||||
| @ -332,7 +332,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On | ||||
|     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, | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| } | ||||
| 
 | ||||
| li.nav-item.active { | ||||
|   background-color: #653b9c; | ||||
|   background-color: var(--tertiary); | ||||
| } | ||||
| 
 | ||||
| fa-icon { | ||||
| @ -47,7 +47,7 @@ li.nav-item { | ||||
| } | ||||
| 
 | ||||
| .navbar-nav { | ||||
|   background: #212121; | ||||
|   background: var(--navbar-bg); | ||||
|   bottom: 0; | ||||
|   box-shadow: 0px 0px 15px 0px #000; | ||||
|   flex-direction: row; | ||||
| @ -91,6 +91,10 @@ li.nav-item { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .dropdown-container { | ||||
|   margin-top: 5px; | ||||
| } | ||||
| 
 | ||||
| nav { | ||||
|   box-shadow: 0px 0px 15px 0px #000; | ||||
| } | ||||
| @ -108,23 +112,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 { | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
| 
 | ||||
|   <div class="item"> | ||||
|     <a class="title-link" [routerLink]="['/audit/wallet/utxos' | relativeUrl]" [fragment]="'expired'"> | ||||
|       <h5 class="card-title"><ng-container i18n="liquid.total-expired">Total Expired</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|       <h5 class="card-title"><ng-container i18n="liquid.total-expired">Total Expired</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|     </a> | ||||
|     <div *ngIf="(stats$ | async) as expiredStats; else loadingData" class="card-text"> | ||||
|       <div class="fee-text" i18n-ngbTooltip="liquid.expired-utxos" ngbTooltip="Total amount of BTC held in Federation UTXOs that have expired timelocks" placement="top">{{ (+expiredStats.all.total) / 100000000 | number: '1.5-5' }} <span style="color: #b86d12;">BTC</span></div> | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     } | ||||
| 
 | ||||
|     .card-title { | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       font-size: 10px; | ||||
|       margin-bottom: 4px;   | ||||
|       font-size: 1rem; | ||||
|  | ||||
| @ -33,7 +33,7 @@ tr, td, th { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .address { | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|   <div class="fee-estimation-container"> | ||||
|     <div class="item"> | ||||
|       <a class="title-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]"> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|       </a> | ||||
|       <div class="card-text"> | ||||
|         <div class="fee-text">{{ federationWalletStats.address_count }} <span i18n="shared.addresses">addresses</span></div> | ||||
| @ -16,7 +16,7 @@ | ||||
|   <div class="fee-estimation-container loading-container"> | ||||
|     <div class="item"> | ||||
|       <a class="title-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]"> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|       </a> | ||||
|       <div class="card-text"> | ||||
|         <div class="skeleton-loader"></div> | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| 
 | ||||
|     .card-title { | ||||
|       margin: 0; | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       font-size: 10px; | ||||
|       font-size: 1rem; | ||||
|       white-space: nowrap; | ||||
|  | ||||
| @ -24,7 +24,7 @@ tr, td, th { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .txid { | ||||
|  | ||||
| @ -27,7 +27,7 @@ tr, td, th { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .transaction { | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|   <div class="fee-estimation-container"> | ||||
|     <div class="item"> | ||||
|       <a class="title-link" [routerLink]="['/audit/pegs' | relativeUrl]"> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| @ -26,7 +26,7 @@ | ||||
|   <div class="fee-estimation-container loading-container"> | ||||
|     <div class="item"> | ||||
|       <a class="title-link" [routerLink]="['/audit/pegs' | relativeUrl]"> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|         <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| 
 | ||||
|     .card-title { | ||||
|       margin: 0; | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       font-size: 10px; | ||||
|       font-size: 1rem; | ||||
|       white-space: nowrap; | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
| 
 | ||||
|   <div class="item"> | ||||
|     <!-- <a class="title-link" [routerLink]="['/audit/emergency-spends' | relativeUrl]"> | ||||
|       <h5 class="card-title"><ng-container i18n="liquid.forfeited-utxos">Forfeited UTXOs</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||
|       <h5 class="card-title"><ng-container i18n="liquid.forfeited-utxos">Forfeited UTXOs</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: var(--title-fg)"></fa-icon></h5> | ||||
|     </a> --> | ||||
|     <h5 class="card-title" i18n="liquid.emergency-keys">Emergency Keys</h5> | ||||
|     <div *ngIf="(emergencyUtxosStats$ | async) as emergencyUtxosStats; else loadingData" class="card-text"> | ||||
|  | ||||
| @ -22,7 +22,7 @@ | ||||
| 
 | ||||
|     .card-title { | ||||
|       margin-bottom: 4px; | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       font-size: 10px; | ||||
|       font-size: 1rem; | ||||
|       white-space: nowrap; | ||||
|  | ||||
| @ -141,7 +141,7 @@ export class ReservesRatioComponent implements OnInit, OnChanges { | ||||
|             show: true, | ||||
|             offsetCenter: [0, '-127%'], | ||||
|             fontSize: 18, | ||||
|             color: '#4a68b9', | ||||
|             color: 'var(--title-fg)', | ||||
|             fontFamily: 'inherit', | ||||
|             fontWeight: 500, | ||||
|           }, | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     } | ||||
| 
 | ||||
|     .card-title { | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       font-size: 10px; | ||||
|       margin-bottom: 4px;   | ||||
|       font-size: 1rem; | ||||
|  | ||||
| @ -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; | ||||
|   } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| } | ||||
| 
 | ||||
| li.nav-item.active { | ||||
|   background-color: #653b9c; | ||||
|   background-color: var(--tertiary); | ||||
| } | ||||
| 
 | ||||
| fa-icon { | ||||
| @ -58,7 +58,7 @@ li.nav-item { | ||||
| } | ||||
| 
 | ||||
| .navbar-nav { | ||||
|   background: #212121; | ||||
|   background: var(--navbar-bg); | ||||
|   bottom: 0; | ||||
|   box-shadow: 0px 0px 15px 0px #000; | ||||
|   flex-direction: row; | ||||
| @ -139,23 +139,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; | ||||
| } | ||||
|  | ||||
| @ -106,7 +106,7 @@ | ||||
| } | ||||
| 
 | ||||
| .black-background { | ||||
|   background-color: #11131f; | ||||
|   background-color: var(--active-bg); | ||||
|   z-index: 100; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| @ -4,12 +4,13 @@ import { MempoolBlock } from '../../interfaces/websocket.interface'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { map, switchMap, tap } 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, MempoolPosition } from '../../interfaces/node-api.interface'; | ||||
| import { animate, style, transition, trigger } from '@angular/animations'; | ||||
| import { ThemeService } from '../../services/theme.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-mempool-blocks', | ||||
| @ -84,6 +85,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   constructor( | ||||
|     private router: Router, | ||||
|     public stateService: StateService, | ||||
|     private themeService: ThemeService, | ||||
|     private cd: ChangeDetectorRef, | ||||
|     private relativeUrlPipe: RelativeUrlPipe, | ||||
|     private location: Location, | ||||
| @ -354,7 +356,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, 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) => { | ||||
|  | ||||
| @ -432,7 +432,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|         splitLine: { | ||||
|           lineStyle: { | ||||
|             type: 'dotted', | ||||
|             color: '#ffffff66', | ||||
|             color: 'var(--transparent-fg)', | ||||
|             opacity: 0.25, | ||||
|           } | ||||
|         } | ||||
| @ -500,7 +500,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, | ||||
|  | ||||
| @ -34,7 +34,7 @@ | ||||
| 
 | ||||
| .sidenav.open { | ||||
|   display: block; | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
| } | ||||
| 
 | ||||
| :host-context(.ltr-layout) .sidenav.open { | ||||
| @ -56,7 +56,7 @@ | ||||
| .sidenav nav { | ||||
|   width: 100%; | ||||
|   height: calc(100vh - 65px); | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
|   padding-left: 20px; | ||||
|   padding-right: 20px; | ||||
|   padding-top: 20px; | ||||
| @ -75,7 +75,7 @@ | ||||
| } | ||||
| 
 | ||||
| .badge-og { | ||||
|   background-color: #4a68b9; | ||||
|   background-color: var(--title-fg); | ||||
| } | ||||
| 
 | ||||
| .badge-pleb { | ||||
| @ -87,7 +87,7 @@ | ||||
| } | ||||
| 
 | ||||
| .badge-whale { | ||||
|   background-color: #653b9c; | ||||
|   background-color: var(--tertiary); | ||||
| } | ||||
| 
 | ||||
| .badge-silver { | ||||
| @ -99,5 +99,5 @@ | ||||
| } | ||||
| 
 | ||||
| .badge-platinum { | ||||
|   background-color: #653b9c; | ||||
|   background-color: var(--tertiary); | ||||
| } | ||||
| @ -55,7 +55,7 @@ | ||||
|           <a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]"> | ||||
|             <h5 class="card-title d-inline" i18n="dashboard.recent-blocks">Recent 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> | ||||
| @ -69,7 +69,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> | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| } | ||||
| 
 | ||||
| .card { | ||||
|   background-color: #1d1f31; | ||||
|   background-color: var(--bg); | ||||
| } | ||||
| 
 | ||||
| .graph-card { | ||||
| @ -32,10 +32,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 { | ||||
|  | ||||
| @ -99,7 +99,7 @@ | ||||
|     } | ||||
|     .card-title { | ||||
|       font-size: 1rem; | ||||
|       color: #4a68b9; | ||||
|       color: var(--title-fg); | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
| @ -107,7 +107,7 @@ | ||||
|     .card-text { | ||||
|       font-size: 18px; | ||||
|       span { | ||||
|         color: #ffffff66; | ||||
|         color: var(--transparent-fg); | ||||
|         font-size: 12px; | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -146,7 +146,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, | ||||
|         }, | ||||
| @ -156,7 +156,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: () => { | ||||
| @ -186,7 +186,7 @@ export class PoolRankingComponent implements OnInit { | ||||
|       name:  $localize`Other (${percentage})`, | ||||
|       label: { | ||||
|         overflow: 'none', | ||||
|         color: '#b1b1b1', | ||||
|         color: 'var(--tooltip-grey)', | ||||
|         alignTo: 'edge', | ||||
|         edgeDistance: edgeDistance | ||||
|       }, | ||||
| @ -195,7 +195,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: () => { | ||||
| @ -306,7 +306,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; | ||||
|  | ||||
| @ -88,7 +88,7 @@ div.scrollable { | ||||
| } | ||||
| 
 | ||||
| .progress { | ||||
|   background-color: #2d3348; | ||||
|   background-color: var(--secondary); | ||||
| } | ||||
| 
 | ||||
| .coinbase { | ||||
| @ -190,7 +190,7 @@ div.scrollable { | ||||
| } | ||||
| 
 | ||||
| .data-title { | ||||
|   color: #4a68b9; | ||||
|   color: var(--title-fg); | ||||
|   font-size: 14px; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -175,7 +175,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', | ||||
|  | ||||
| @ -25,7 +25,7 @@ | ||||
|   } | ||||
| 
 | ||||
|   .timeline-wrapper.mined { | ||||
|     border: solid 4px #1a9436; | ||||
|     border: solid 4px var(--success); | ||||
|   } | ||||
| 
 | ||||
|   .no-replacements { | ||||
|  | ||||
| @ -15,12 +15,12 @@ | ||||
| 
 | ||||
|   &::before { | ||||
|     left: 0; | ||||
|     background: linear-gradient(to right, #24273e, #24273e, transparent); | ||||
|     background: linear-gradient(to right, var(--box-bg), var(--box-bg), transparent); | ||||
|   } | ||||
| 
 | ||||
|   &::after { | ||||
|     right: 0; | ||||
|     background: linear-gradient(to left, #24273e, #24273e, transparent); | ||||
|     background: linear-gradient(to left, var(--box-bg), var(--box-bg), transparent); | ||||
|   } | ||||
| 
 | ||||
|   .timeline-wrapper { | ||||
| @ -45,7 +45,7 @@ | ||||
|       width: 100%; | ||||
|       height: 70px; | ||||
|       top: -70px; | ||||
|       background: linear-gradient(to bottom, rgba(36, 39, 62, 0) 0%, rgba(36, 39, 62, 1) 100%); | ||||
|       background: linear-gradient(to bottom, var(--fade-out-box-bg-start), var(--fade-out-box-bg-end)); | ||||
|       z-index: 1; | ||||
|     } | ||||
|   } | ||||
| @ -101,7 +101,7 @@ | ||||
|       right: -5px; | ||||
|       top: 0; | ||||
|       transform: translateY(-50%); | ||||
|       background: #105fb0; | ||||
|       background: var(--primary); | ||||
|       border-radius: 5px; | ||||
| 
 | ||||
|       &.left { | ||||
| @ -112,7 +112,7 @@ | ||||
|       } | ||||
| 
 | ||||
|       &.fullrbf { | ||||
|         background: #1bd8f4; | ||||
|         background: var(--info); | ||||
|       } | ||||
|     } | ||||
|     &.first-node { | ||||
| @ -165,20 +165,20 @@ | ||||
| 
 | ||||
|       &.mined { | ||||
|         .shape-border { | ||||
|           background: #1a9436; | ||||
|           background: var(--success); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .shape-border:hover { | ||||
|         padding: 0px; | ||||
|         .shape { | ||||
|           background: #1bd8f4; | ||||
|           background: var(--info); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &.selected.mined { | ||||
|         .shape-border { | ||||
|           background: #1a9436; | ||||
|           background: var(--success); | ||||
|           height: calc(1em + 16px); | ||||
|           width: calc(1em + 16px); | ||||
| 
 | ||||
| @ -190,7 +190,7 @@ | ||||
|             padding: 4px; | ||||
|             .shape { | ||||
|               border-width: 1px; | ||||
|               border-color: #1bd8f4 | ||||
|               border-color: var(--info) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
| @ -207,9 +207,9 @@ | ||||
|         width: 20px; | ||||
|         height: 108px; | ||||
|         bottom: 50%; | ||||
|         border-right: solid 10px #105fb0; | ||||
|         border-right: solid 10px var(--primary); | ||||
|         &.fullrbf { | ||||
|           border-right: solid 10px #1bd8f4; | ||||
|           border-right: solid 10px var(--info); | ||||
|         } | ||||
|         &.last-pipe { | ||||
|           height: 150px; | ||||
| @ -218,10 +218,10 @@ | ||||
|       } | ||||
| 
 | ||||
|       .corner { | ||||
|         border-bottom: solid 10px #105fb0; | ||||
|         border-bottom: solid 10px var(--primary); | ||||
|         border-bottom-right-radius: 10px; | ||||
|         &.fullrbf { | ||||
|           border-bottom: solid 10px #1bd8f4; | ||||
|           border-bottom: solid 10px var(--info); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -32,7 +32,7 @@ | ||||
|     cursor: pointer; | ||||
|     opacity: 0.8; | ||||
|     transition: opacity 500ms; | ||||
|     background: radial-gradient(#1d1f31 0%, transparent 50%); | ||||
|     background: radial-gradient(var(--bg) 0%, transparent 50%); | ||||
| 
 | ||||
|     &:hover { | ||||
|       opacity: 1; | ||||
|  | ||||
| @ -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"> | ||||
|     <select formControlName="theme" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" (change)="changeTheme()"> | ||||
|         <option value="default" i18n="theme.mempool-theme">Classic</option> | ||||
|         <option value="contrast" i18n="theme.high-contrast">BlueMatt</option> | ||||
|     </select> | ||||
| </div> | ||||
| @ -0,0 +1,3 @@ | ||||
| .custom-select { | ||||
|   width: 100px; | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user