diff --git a/app/App.js b/app/App.js index 3462039..8352db2 100644 --- a/app/App.js +++ b/app/App.js @@ -8,46 +8,43 @@ import { PSDENOMINATIONS, COLORS, PAINT } from './constants'; export default class App { constructor() { + this.blockRefs = []; + this.mempoolRefs = []; + this.blockList = document.getElementById('blockList'); + this.connectionStatus = document.getElementById('connectionStatus'); + this.hero = document.getElementById('hero'); + this.blockColors = ['000000']; } async init() { - this.domRefList = []; - this.blockList = document.getElementById('blockList'); - this.blockList.style.setProperty('--private-color', COLORS.private); - this.blockList.style.setProperty('--instant-color', COLORS.instant); - this.connectionStatus = document.getElementById('connectionStatus') - this.currentBlock = document.createElement('div'); - this.currentBlock.className = 'block'; - this.blockList.appendChild(this.currentBlock); - this.blockColors = ['000000']; - const block = (new URL(window.location)).searchParams.get('block'); if (block != null) { // display one block - this.currentBlock.classList.add('solo'); + this.hero.classList.add('solo'); this.connectionStatus.className = 'is-loading'; var txs = []; var pages = 1; var prevHash = null; + const txListener = this.onTransactionBuilder(this.hero, false); for (let i = 0; i < pages; ++i) { await fetch(`https://insight.dash.org/insight-api/txs?block=${block}&pageNum=${i}`) .then(resp => resp.json()) .then(thisBlockData => { - // console.log({i, pages, prevHash, thisBlockData}); if (!prevHash && thisBlockData.txs.length > 0) { return fetch('https://insight.dash.org/insight-api/block-index/'+(thisBlockData.txs[0].blockheight - 1)) .then(resp => resp.json()) .then(prevBlockData => { prevHash = prevBlockData.blockHash; this.blockColors = App.generateColors(prevHash); + this.applyColors(this.hero); pages = thisBlockData.pagesTotal; for (let j = 0; j < thisBlockData.txs.length; ++j) { - this.onTransaction(thisBlockData.txs[j]); + txListener(thisBlockData.txs[j]); } }); } else { for (let j = 0; j < thisBlockData.txs.length; ++j) { - this.onTransaction(thisBlockData.txs[j]); + txListener(thisBlockData.txs[j]); } } }); @@ -58,6 +55,7 @@ export default class App { .then(resp => resp.json()) .then(data => { this.blockColors = App.generateColors(data.lastblockhash); + this.applyColors(this.hero); }); this.socket = io.connect("https://insight.dash.org:443/"); @@ -66,7 +64,7 @@ export default class App { // Join the room. this.socket.emit('subscribe', 'inv'); }) - this.socket.on('tx', this.onTransaction.bind(this)); + this.socket.on('tx', this.onTransactionBuilder(this.hero, true).bind(this)); this.socket.on('block', this.onBlock.bind(this)); this.socket.on('disconnect', () => { this.connectionStatus.className = 'is-disconnected'; @@ -78,48 +76,71 @@ export default class App { } static generateColors(blockHash) { - // https://github.com/c0bra/color-scheme-js - const schemeTypes = [ - 'contrast', - 'triade', - 'triade', - 'tetrade', - 'tetrade', - 'analogic', - 'analogic', - 'analogic', - 'analogic', - ]; const hue = Math.floor( parseInt(blockHash.slice(-3), 16) / 4096 * 360 ); - const schemeFraction = parseInt(blockHash.slice(-5, -3), 16) / 256; - const scheme = schemeTypes[Math.floor(schemeFraction * schemeTypes.length)]; var blockColorScheme = new ColorScheme(); - blockColorScheme.from_hue(hue).scheme(scheme).add_complement(true); + blockColorScheme.from_hue(hue).scheme('analogic').add_complement(true); const colors = blockColorScheme.colors(); - console.log('New color scheme: ' + scheme + ' based on %chue ' + hue, 'background-color:#'+colors[0]); return colors; } + applyColors(target) { + for (var i in this.blockColors) { + const color = this.blockColors[i]; + target.style.setProperty(`--color-${i}`, '#'+color); + } + } + onBlock(data) { + var completedBlock = document.createElement('div'); + completedBlock.className = 'block'; + completedBlock.id = data; + this.applyColors(completedBlock); this.blockColors = App.generateColors(data); + this.applyColors(this.hero); var blockLink = document.createElement('a'); blockLink.className = 'explorer-link'; blockLink.href = document.location + '?block=' + data; blockLink.target = '_blank'; blockLink.setAttribute('rel', 'noopener'); blockLink.appendChild(document.createTextNode('🗗')); - this.currentBlock.appendChild(blockLink); - this.currentBlock = document.createElement('div'); - this.currentBlock.className = 'block'; - - if (this.domRefList.unshift(this.currentBlock) > 16) { - var toDelete = this.domRefList.pop(); - toDelete.remove(); - } - this.blockList.insertBefore(this.currentBlock, this.blockList.firstChild); + fetch('https://insight.dash.org/insight-api/block/' + data) + .then(resp => resp.json()) + .then(data => { + let mined = []; + for (var i in data.tx) { + const txid = data.tx[i]; + let paint = document.getElementById(txid); + if (paint) { + mined.push(paint); + completedBlock.insertBefore(paint, completedBlock.firstChild); + } + } + this.mempoolRefs = this.mempoolRefs.filter(item => !mined.includes(item)); + this.mempoolRefs.forEach(item => { + item.classList.add('stale'); + item.data_ignored = item.data_ignored ? item.data_ignored + 1 : 1; + }); + this.mempoolRefs.filter(item => { + if (item.data_ignored > 4) { + try { + this.hero.removeChild(item); + } catch (err) { } + return false; + } + return true; + }); + completedBlock.appendChild(blockLink); + if (this.blockRefs.unshift(this.completedBlock) > 8) { + let toDelete = this.blockRefs.pop(); + if (toDelete) { + toDelete.remove(); + } + } + this.blockList.insertBefore(completedBlock, this.blockList.firstChild); + }); } static isPrivateSend(components) { @@ -132,36 +153,45 @@ export default class App { }); } - onTransaction(data) { - const isMixing = App.isPrivateSend(data.vout); - const isInstant = data.txlock || (data.vin && data.vin.length <= 4); - const tx = { - mixing: isMixing, - instant: isInstant, - value: data.valueOut, - x: parseInt(data.txid.slice(0, 4), 16) / 65536, - y: parseInt(data.txid.slice(4, 8), 16) / 65536, - rotation: parseInt(data.txid.slice(16, 17), 16) / 16, - paintIndex: parseInt(data.txid.slice(17, 21), 16) / 65536, - color: isMixing ? COLORS.private : isInstant ? COLORS.instant : this.blockColors[ - Math.floor(parseInt(data.txid.slice(21, 23), 16) / 256 * this.blockColors.length) - ] - }; + onTransactionBuilder(target, addToMempool = false) { + return (data) => { + const isMixing = App.isPrivateSend(data.vout); + const isInstant = data.txlock || (data.vin && data.vin.length <= 4); + const isSimple = data.txlock || (data.vin && data.vin.length <= 1); + const tx = { + id: data.txid, + mixing: isMixing, + instant: isInstant, + simple: isSimple, + value: data.valueOut, + x: parseInt(data.txid.slice(0, 4), 16) / 65536, + y: parseInt(data.txid.slice(4, 8), 16) / 65536, + rotation: parseInt(data.txid.slice(16, 17), 16) / 16, + paintIndex: parseInt(data.txid.slice(17, 21), 16) / 65536, + color: isMixing ? COLORS.black : !isSimple ? COLORS.white : + 'var(--color-'+ + Math.floor(parseInt(data.txid.slice(21, 23), 16) / 256 * this.blockColors.length)+ + ')' + }; - console.log('tx: '+tx.value+(tx.mixing?' mixing':'')+(tx.instant?' instant':'')); - - var paint = document.createElement('div'); - paint.classList.add('paint'); - paint.style.maskImage = 'url(assets/paint/' + (tx.value > 10 ? - PAINT.big[Math.floor(tx.paintIndex * 12)] : - PAINT.small[Math.floor(tx.paintIndex * 11)] - ) + ')'; - paint.style.setProperty('-webkit-mask-image', paint.style.maskImage); - paint.style.setProperty('--x', tx.x); - paint.style.setProperty('--y', tx.y); - paint.style.setProperty('--size', Math.log(1 + tx.value)/Math.log(2)); - paint.style.setProperty('--rotation', tx.rotation * 360 + 'deg'); - paint.style.setProperty('--color', '#'+tx.color); - this.currentBlock.appendChild(paint, this.currentBlock.firstChild); + var paint = document.createElement('div'); + paint.id = tx.id; + paint.classList.add('paint'); + paint.style.maskImage = 'url(assets/paint/' + (tx.value > 10 ? + PAINT.big[Math.floor(tx.paintIndex * 12)] : + PAINT.small[Math.floor(tx.paintIndex * 11)] + ) + ')'; + paint.style.setProperty('-webkit-mask-image', paint.style.maskImage); + paint.style.setProperty('--x', tx.x); + paint.style.setProperty('--y', tx.y); + paint.style.setProperty('--size', Math.log(1 + tx.value)/Math.log(2)); + paint.style.setProperty('--rotation', tx.rotation * 360 + 'deg'); + paint.style.setProperty('--color', tx.color); + if (addToMempool && this.mempoolRefs.unshift(paint) > 200) { + let toDelete = this.mempoolRefs.pop(); + toDelete.remove(); + } + target.appendChild(paint); + } } }; diff --git a/app/assets/index.html b/app/assets/index.html index 8ea8d7b..6da6c95 100644 --- a/app/assets/index.html +++ b/app/assets/index.html @@ -8,6 +8,7 @@
+
diff --git a/app/constants.js b/app/constants.js index 1cca557..e2c0886 100644 --- a/app/constants.js +++ b/app/constants.js @@ -7,8 +7,8 @@ export const PSDENOMINATIONS = [ ]; export const COLORS = { - private: '000000', - instant: 'ffffff' + black: '#000000', + white: '#ffffff' }; export const PAINT = { diff --git a/app/styles/main.css b/app/styles/main.css index de5fba0..f847e77 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -34,7 +34,17 @@ a { margin-left: 80vw; padding-top: 2.5vw; } -.block:first-child { +#hero, .block { + width: 15vw; + height: 15vw; + -webkit-box-shadow: 0.1em 0.1em 1em hsla(0, 0%, 0%, 0.5); + box-shadow: 0.1em 0.1em 1em hsla(0, 0%, 0%, 0.5); + background-color: #dad7b7; + margin: 0 auto 1em; + position: relative; + overflow: hidden; +} +#hero { position: absolute; left: 41.125%; top: 50%; @@ -45,26 +55,16 @@ a { transform: translate(-50%,-50%); } @media (max-height: 82.5vw) { - .block:first-child { + #hero { width: calc(100vh - 5vw); height: calc(100vh - 5vw); } } -.block.solo { +#hero.solo { left: 50%; width: 95vmin; height: 95vmin; } -.block { - width: 15vw; - height: 15vw; - -webkit-box-shadow: 0.1em 0.1em 1em hsla(0, 0%, 0%, 0.5); - box-shadow: 0.1em 0.1em 1em hsla(0, 0%, 0%, 0.5); - background-color: #dad7b7; - margin: 0 auto 1em; - position: relative; - overflow: hidden; -} .explorer-link { display: none; position: absolute;