mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Defer output spend loading
This commit is contained in:
parent
3f748cd6c4
commit
a8a945e77e
@ -8,7 +8,7 @@ import { formatCurrency } from '../utils/fx.js'
|
|||||||
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
||||||
import { SPKToAddress } from '../utils/encodings.js'
|
import { SPKToAddress } from '../utils/encodings.js'
|
||||||
import api from '../utils/api.js'
|
import api from '../utils/api.js'
|
||||||
import { searchTx, searchBlockHash, searchBlockHeight } from '../utils/search.js'
|
import { searchTx, searchBlockHash, searchBlockHeight, fetchSpends } from '../utils/search.js'
|
||||||
import { fade } from 'svelte/transition'
|
import { fade } from 'svelte/transition'
|
||||||
|
|
||||||
function onClose () {
|
function onClose () {
|
||||||
@ -148,6 +148,23 @@ $: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let spends
|
||||||
|
let loadingSpends = false
|
||||||
|
$: {
|
||||||
|
if ($detailTx && $detailTx.id) {
|
||||||
|
spends = null
|
||||||
|
loadSpends($detailTx.id)
|
||||||
|
} else {
|
||||||
|
spends = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSpends(txid, reload = false) {
|
||||||
|
if (!reload) loadingSpends = true
|
||||||
|
spends = await fetchSpends(txid)
|
||||||
|
loadingSpends = false
|
||||||
|
}
|
||||||
|
|
||||||
function calcSankeyLines(inputs, outputs, fee, value, totalHeight, svgWidth, flowWeight) {
|
function calcSankeyLines(inputs, outputs, fee, value, totalHeight, svgWidth, flowWeight) {
|
||||||
const total = fee + value
|
const total = fee + value
|
||||||
const mergeOffset = (totalHeight - flowWeight) / 2
|
const mergeOffset = (totalHeight - flowWeight) / 2
|
||||||
@ -269,10 +286,10 @@ async function goToInput(e, input) {
|
|||||||
loading.decrement()
|
loading.decrement()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function goToOutput(e, output) {
|
async function goToSpend(e, spend) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
loading.increment()
|
loading.increment()
|
||||||
await searchTx(output.spend.txid, output.spend.vin)
|
await searchTx(spend.txid, spend.vin)
|
||||||
loading.decrement()
|
loading.decrement()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +492,7 @@ async function goToBlock(e) {
|
|||||||
stroke-linejoin: miter;
|
stroke-linejoin: miter;
|
||||||
fill: white;
|
fill: white;
|
||||||
fill-opacity: 0;
|
fill-opacity: 0;
|
||||||
transition: fill-opacity 300ms;
|
transition: fill-opacity 300ms, stroke-opacity 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.right {
|
&.right {
|
||||||
@ -488,6 +505,16 @@ async function goToBlock(e) {
|
|||||||
fill-opacity: 1;
|
fill-opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
.chevron .outline {
|
||||||
|
stroke-opacity: 0;
|
||||||
|
animation-name: svgpulse;
|
||||||
|
animation-delay: 500ms;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +590,20 @@ async function goToBlock(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes svgpulse {
|
||||||
|
0% {
|
||||||
|
fill-opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
fill-opacity: 30%
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
fill-opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<Overlay name="tx" on:close={onClose}>
|
<Overlay name="tx" on:close={onClose}>
|
||||||
@ -701,8 +742,14 @@ async function goToBlock(e) {
|
|||||||
<p class="header">{$detailTx.outputs.length} output{$detailTx.outputs.length > 1 ? 's' : ''} {#if $detailTx.fee}+ fee{/if}</p>
|
<p class="header">{$detailTx.outputs.length} output{$detailTx.outputs.length > 1 ? 's' : ''} {#if $detailTx.fee}+ fee{/if}</p>
|
||||||
{#each outputs as output}
|
{#each outputs as output}
|
||||||
<div class="entry" class:clickable={output.rest} class:highlight={highlight.out != null && highlight.out === output.index} on:click={() => clickItem(output)}>
|
<div class="entry" class:clickable={output.rest} class:highlight={highlight.out != null && highlight.out === output.index} on:click={() => clickItem(output)}>
|
||||||
{#if output.spend}
|
{#if loadingSpends}
|
||||||
<a href="/tx/{output.spend.vin}:{output.spend.txid}" on:click={(e) => goToOutput(e, output)} class="put-link" transition:fade|local={{ duration: 200 }}>
|
<span class="put-link loading" out:fade|local={{ duration: 500 }}>
|
||||||
|
<svg class="chevron right" height="1.2em" width="1.2em" viewBox="0 0 512 512">
|
||||||
|
<path d="M 107.628,257.54 327.095,38.078 404,114.989 261.506,257.483 404,399.978 327.086,476.89 Z" class="outline" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
{:else if spends[output.index]}
|
||||||
|
<a href="/tx/{spends[output.index].vin}:{spends[output.index].txid}" on:click={(e) => goToSpend(e, spends[output.index])} class="put-link" in:fade|local={{ duration: 200 }}>
|
||||||
<svg class="chevron right" height="1.2em" width="1.2em" viewBox="0 0 512 512">
|
<svg class="chevron right" height="1.2em" width="1.2em" viewBox="0 0 512 512">
|
||||||
<path d="M 107.628,257.54 327.095,38.078 404,114.989 261.506,257.483 404,399.978 327.086,476.89 Z" class="outline" />
|
<path d="M 107.628,257.54 327.095,38.078 404,114.989 261.506,257.483 404,399.978 327.086,476.89 Z" class="outline" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -422,8 +422,6 @@ export default class TxController {
|
|||||||
await searchTx(selected.id)
|
await searchTx(selected.id)
|
||||||
loading.decrement()
|
loading.decrement()
|
||||||
} else {
|
} else {
|
||||||
const spendResult = await fetchSpends(selected.id)
|
|
||||||
if (spendResult) selected = addSpends(selected, spendResult)
|
|
||||||
urlPath.set(`/tx/${selected.id}`)
|
urlPath.set(`/tx/${selected.id}`)
|
||||||
detailTx.set(selected)
|
detailTx.set(selected)
|
||||||
overlay.set('tx')
|
overlay.set('tx')
|
||||||
|
@ -175,7 +175,17 @@ async function fetchSpends (txid) {
|
|||||||
})
|
})
|
||||||
if (!response) throw new Error('null response')
|
if (!response) throw new Error('null response')
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
return response.json()
|
const result = await response.json()
|
||||||
|
return result.map(output => {
|
||||||
|
if (output) {
|
||||||
|
return {
|
||||||
|
txid: output[0],
|
||||||
|
vin: output[1],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -207,9 +217,7 @@ export async function searchTx (txid, input, output) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let searchResult = await fetchTx(txid)
|
let searchResult = await fetchTx(txid)
|
||||||
const spendResult = await fetchSpends(txid)
|
|
||||||
if (searchResult) {
|
if (searchResult) {
|
||||||
if (spendResult) searchResult = addSpends(searchResult, spendResult)
|
|
||||||
selectedTx.set(searchResult)
|
selectedTx.set(searchResult)
|
||||||
detailTx.set(searchResult)
|
detailTx.set(searchResult)
|
||||||
overlay.set('tx')
|
overlay.set('tx')
|
||||||
|
@ -50,7 +50,7 @@ defmodule BitcoinStream.Router do
|
|||||||
match "/api/tx/:hash" do
|
match "/api/tx/:hash" do
|
||||||
case get_tx(hash) do
|
case get_tx(hash) do
|
||||||
{:ok, tx} ->
|
{:ok, tx} ->
|
||||||
put_resp_header(conn, "cache-control", "public, max-age=60, immutable")
|
put_resp_header(conn, "cache-control", "public, max-age=300, immutable")
|
||||||
|> send_resp(200, tx)
|
|> send_resp(200, tx)
|
||||||
_ ->
|
_ ->
|
||||||
Logger.debug("Error getting tx hash");
|
Logger.debug("Error getting tx hash");
|
||||||
|
Loading…
Reference in New Issue
Block a user