diff --git a/client/public/global.css b/client/public/global.css index 936fd9a..ecceba9 100644 --- a/client/public/global.css +++ b/client/public/global.css @@ -48,6 +48,8 @@ --twitter-blue: #1da1f2; --monospace-purple: darkviolet; + + --loading-color: var(--palette-x); } .light-mode { diff --git a/client/src/components/SearchBar.svelte b/client/src/components/SearchBar.svelte index 62acf43..3c9b541 100644 --- a/client/src/components/SearchBar.svelte +++ b/client/src/components/SearchBar.svelte @@ -9,7 +9,7 @@ import AddressIcon from '../assets/icon/cil-wallet.svg' import TxIcon from '../assets/icon/cil-arrow-circle-right.svg' import { fly } from 'svelte/transition' import { matchQuery, searchTx, searchBlock } from '../utils/search.js' -import { selectedTx, detailTx, overlay } from '../stores.js' +import { selectedTx, detailTx, overlay, loading } from '../stores.js' let query let matchedQuery @@ -26,19 +26,21 @@ async function searchSubmit (e) { e.preventDefault() if (matchedQuery) { + $loading++ switch(matchedQuery.query) { case 'txid': - searchTx(matchedQuery.txid) + await searchTx(matchedQuery.txid) break; case 'input': - searchTx(matchedQuery.txid, matchedQuery.input, null) + await searchTx(matchedQuery.txid, matchedQuery.input, null) break; case 'output': - searchTx(matchedQuery.txid, null, matchedQuery.output) + await searchTx(matchedQuery.txid, null, matchedQuery.output) break; } + $loading-- } return false diff --git a/client/src/components/TransactionOverlay.svelte b/client/src/components/TransactionOverlay.svelte index 735cb29..fef72c0 100644 --- a/client/src/components/TransactionOverlay.svelte +++ b/client/src/components/TransactionOverlay.svelte @@ -3,7 +3,7 @@ import Overlay from '../components/Overlay.svelte' import Icon from './Icon.svelte' import BookmarkIcon from '../assets/icon/cil-bookmark.svg' import { longBtcFormat, numberFormat, feeRateFormat, dateFormat } from '../utils/format.js' -import { exchangeRates, settings, sidebarToggle, newHighlightQuery, highlightingFull, detailTx, pageWidth, latestBlockHeight, highlightInOut } from '../stores.js' +import { exchangeRates, settings, sidebarToggle, newHighlightQuery, highlightingFull, detailTx, pageWidth, latestBlockHeight, highlightInOut, loading } from '../stores.js' import { formatCurrency } from '../utils/fx.js' import { hlToHex, mixColor, teal, purple } from '../utils/color.js' import { SPKToAddress } from '../utils/encodings.js' @@ -249,11 +249,17 @@ function getMiterOffset (weight, dy, dx) { } else return 0 } -function clickItem (item) { +async function clickItem (item) { if (item.rest) { truncate = false } else if (item.prev_txid && item.prev_vout != null) { - searchTx(item.prev_txid, null, item.prev_vout) + $loading++ + await searchTx(item.prev_txid, null, item.prev_vout) + $loading-- + } else if (item.spend && item.spend.txid && item.spend.vin) { + $loading++ + await searchTx(item.spend.txid, item.spend.vin) + $loading-- } } @@ -265,8 +271,14 @@ function clickItem (item) { text-align: left; h2 { + margin: 0 0 1em; font-size: 1.2em; word-break: break-word; + + .title { + font-size: 1.25em; + white-space: nowrap; + } } .tx-id { @@ -277,7 +289,7 @@ function clickItem (item) { .icon-button { float: right; - font-size: 24px; + font-size: 1.1em; margin: 0; transition: opacity 300ms, color 300ms, background 300ms; background: var(--palette-d); @@ -299,7 +311,7 @@ function clickItem (item) { padding: 4px 8px; border-radius: 8px; float: right; - margin: 5px; + margin: 0 5px; color: white; font-weight: bold; @@ -413,7 +425,7 @@ function clickItem (item) { &.highlight { background: linear-gradient(90deg, var(--bold-a) -100%, transparent 100%); } - &:hover { + &.clickable:hover { background: linear-gradient(90deg, var(--palette-e), transparent); } .address { @@ -425,10 +437,14 @@ function clickItem (item) { &.outputs { .entry { padding-right: 10px; + border-right: solid 1px transparent; + &.unspent { + border-right: solid 1px var(--grey); + } &.highlight { background: linear-gradient(90deg, transparent 0%, var(--bold-a) 200%); } - &:hover { + &.clickable:hover { background: linear-gradient(-90deg, var(--palette-e), transparent); } } @@ -486,7 +502,7 @@ function clickItem (item) { unconfirmed {/if} -

{#if $detailTx.isCoinbase }Coinbase{:else}Transaction{/if} { $detailTx.id }

+

{#if $detailTx.isCoinbase }Coinbase{:else}Transaction{/if} { $detailTx.id }

{#if $detailTx.block}
@@ -599,7 +615,7 @@ function clickItem (item) {

{$detailTx.outputs.length} output{$detailTx.outputs.length > 1 ? 's' : ''} {#if $detailTx.fee != null}+ fee{/if}

{#each outputs as output} -
clickItem(output)}> +
clickItem(output)}>

{output.address.slice(0,-6)}{output.address.slice(-6)}

{ output.value == null ? '???' : formatBTC(output.value) }

diff --git a/client/src/components/TxViz.svelte b/client/src/components/TxViz.svelte index de6610b..438d50c 100644 --- a/client/src/components/TxViz.svelte +++ b/client/src/components/TxViz.svelte @@ -3,7 +3,10 @@ import TxController from '../controllers/TxController.js' import TxRender from './TxRender.svelte' import getTxStream from '../controllers/TxStream.js' - import { settings, overlay, serverConnected, serverDelay, txCount, mempoolCount, mempoolScreenHeight, frameRate, avgFrameRate, blockVisible, tinyScreen, compactScreen, currentBlock, selectedTx, blockAreaSize, devEvents, devSettings, pageWidth, pageHeight } from '../stores.js' + import { settings, overlay, serverConnected, serverDelay, txCount, mempoolCount, + mempoolScreenHeight, frameRate, avgFrameRate, blockVisible, tinyScreen, + compactScreen, currentBlock, selectedTx, blockAreaSize, devEvents, + devSettings, pageWidth, pageHeight, loading } from '../stores.js' import BlockInfo from '../components/BlockInfo.svelte' import SearchBar from '../components/SearchBar.svelte' import TxInfo from '../components/TxInfo.svelte' @@ -12,10 +15,12 @@ import AboutOverlay from '../components/AboutOverlay.svelte' import DonationOverlay from '../components/DonationOverlay.svelte' import SupportersOverlay from '../components/SupportersOverlay.svelte' + import LoadingAnimation from '../components/util/LoadingAnimation.svelte' import Alerts from '../components/alert/Alerts.svelte' import { numberFormat } from '../utils/format.js' import { exchangeRates, lastBlockId, haveSupporters, sidebarToggle } from '../stores.js' import { formatCurrency } from '../utils/fx.js' + import { fade } from 'svelte/transition' import config from '../config.js' let width = window.innerWidth - 20 @@ -426,6 +431,34 @@ } } + .loading-overlay { + position: fixed; + width: 100%; + height: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 999; + background: rgba(0,0,0,0.5); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .loading-wrapper { + width: 100px; + } + + .loading-msg { + margin: .4em 0 0; + font-size: 1em; + font-weight: bold; + color: white; + text-shadow: 0 0 10px black; + } + } + @media screen and (max-width: 640px) { .search-bar-wrapper { position: fixed; @@ -515,6 +548,15 @@ {/if} {/if} + {#if $loading} +
+
+ +

loading

+
+
+ {/if} + {#if config.dev && config.debug && $devSettings.guides }
diff --git a/client/src/components/util/LoadingAnimation.svelte b/client/src/components/util/LoadingAnimation.svelte new file mode 100644 index 0000000..371a9ad --- /dev/null +++ b/client/src/components/util/LoadingAnimation.svelte @@ -0,0 +1,164 @@ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/models/BitcoinTx.js b/client/src/models/BitcoinTx.js index 7942edb..bc3dee3 100644 --- a/client/src/models/BitcoinTx.js +++ b/client/src/models/BitcoinTx.js @@ -15,13 +15,15 @@ export default class BitcoinTx { // number of bytes encoding the block height const height_bytes = parseInt(cbInfo.substring(0,2), 16) // extract the specified number of bytes, reverse the endianness (reverse pairs of hex characters), parse as a hex string - const height = parseInt(cbInfo.substring(2,2 + (height_bytes * 2)).match(/../g).reverse().join(''),16) + const parsed_height = parseInt(cbInfo.substring(2,2 + (height_bytes * 2)).match(/../g).reverse().join(''),16) // save remaining bytes as free data const sig = cbInfo.substring(2 + (height_bytes * 2)) const sigAscii = sig.match(/../g).reduce((parsed, hexChar) => { return parsed + String.fromCharCode(parseInt(hexChar, 16)) }, "") + const height = block.height == null ? parsed_height : block.height + const subsidy = subsidyAt(height) this.coinbase = { diff --git a/client/src/stores.js b/client/src/stores.js index d4054c6..f4bde68 100644 --- a/client/src/stores.js +++ b/client/src/stores.js @@ -167,3 +167,4 @@ export const blocksEnabled = derived([settings], ([$settings]) => { export const latestBlockHeight = writable(null) export const highlightInOut = writable(null) +export const loading = writable(0)