Responsive tables on index and block view
Diff
package-lock.json | 6 ------
frontend/package-lock.json | 1 +
frontend/package.json | 5 +++--
frontend/src/_table.scss | 7 +++++++
frontend/src/lib/Time.svelte | 34 ++++++++++++++++++++++++++++++++++
frontend/src/lib/bitcoinScript.ts | 2 ++
frontend/src/lib/dayjs.ts | 12 ++++++++++++
frontend/src/routes/__layout.svelte | 8 +++++++-
frontend/src/routes/index.svelte | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
web-api/src/methods/block.rs | 22 ++++++++++++++--------
frontend/src/routes/block/[id].svelte | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
11 files changed, 179 insertions(+), 79 deletions(-)
@@ -1,6 +1,0 @@
{
"name": "list-blocks",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}
@@ -9,6 +9,7 @@
"version": "0.0.1",
"dependencies": {
"buffer": "^6.0.3",
"dayjs": "^1.11.2",
"svelte-time": "^0.7.0",
"sveltekit-i18n": "^2.2.1"
},
@@ -35,7 +35,8 @@
"type": "module",
"dependencies": {
"buffer": "^6.0.3",
"sveltekit-i18n": "^2.2.1",
"svelte-time": "^0.7.0"
"svelte-time": "^0.7.0",
"dayjs": "^1.11.2",
"sveltekit-i18n": "^2.2.1"
}
}
@@ -1,3 +1,10 @@
.table-responsive {
@apply block w-full overflow-x-auto;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
table {
@apply border-collapse table-auto w-full;
@@ -1,0 +1,34 @@
<script>
import { dayjs } from "./dayjs";
import { onMount } from "svelte";
export let timestamp = new Date().toISOString();
export let format = "LLL";
export let relative = false;
export let live = false;
export let formatted = "";
let interval = undefined;
const DEFAULT_INTERVAL = 60 * 1000;
onMount(() => {
if (relative && live !== false) {
interval = setInterval(() => {
formatted = dayjs(timestamp).from();
}, Math.abs(typeof live === "number" ? live : DEFAULT_INTERVAL));
}
return () => {
if (typeof interval === "number") {
clearInterval(interval);
}
};
});
$: formatted = relative ? dayjs(timestamp).from() : dayjs(timestamp).format(format);
$: title = relative ? dayjs(timestamp).format(format) : undefined;
</script>
<time {...$$restProps} title={title} datetime={timestamp}>
{formatted}
</time>
@@ -20,8 +20,10 @@
}
if(byte >= 0x02 && byte <= 0x4b) {
out.push("OP_PUSHBYTES_" + byte);
out.push(bytes.slice(i + 1, i + 1 + byte).toString("hex"));
i += byte;
continue;
}
out.push(Operation[byte] || byte.toString(16))
@@ -1,0 +1,12 @@
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
import localizedFormat from "dayjs/plugin/localizedFormat.js";
import 'dayjs/locale/en-gb';
dayjs.locale('en-gb');
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);
export { dayjs };
@@ -36,7 +36,9 @@
</div>
</header>
<slot />
<main>
<slot />
</main>
<style lang="scss">
header {
@@ -64,5 +66,9 @@
}
}
}
}
main {
@apply mx-5;
}
</style>
@@ -18,11 +18,14 @@
<script>
import { t as _ } from "$lib/i18n";
import Time from "svelte-time";
import Time from "$lib/Time.svelte";
// TODO: needs loader
export let blocks = [];
let relativeTimestamps = true;
const toggleTimestamps = () => relativeTimestamps = !relativeTimestamps;
let transactions = [
{
hash: "cd16563757e4504d7f204cb3af0f3f388e6118b4f91e7fc711b3afe8108f7bf2",
@@ -64,29 +67,39 @@
<a class="header-size text-white hover:text-slate-400" href="/block">โ</a>
</div>
<table>
<thead>
<tr>
<th>{$_("home.latest_blocks.table.height")}</th>
<th>{$_("home.latest_blocks.table.timestamp")}</th>
<th>{$_("home.latest_blocks.table.txns")}</th>
<th>{$_("home.latest_blocks.table.size")}</th>
<th>{$_("home.latest_blocks.table.weight")}</th>
</tr>
</thead>
<tbody>
{#each blocks as block}
<div class="table-responsive">
<table class="table-fixed md:table-auto">
<thead>
<tr>
<th><a href={`/block/${block.height}`}>{block.height}</a></th>
<td><Time live relative timestamp={block.timestamp} /></td>
<td>{block.tx_count}</td>
<td>{block.bits}</td>
<td>{block.weight}</td>
<th>{$_("home.latest_blocks.table.height")}</th>
<th>{$_("home.latest_blocks.table.timestamp")}</th>
<th>{$_("home.latest_blocks.table.txns")}</th>
<th>{$_("home.latest_blocks.table.size")}</th>
<th>{$_("home.latest_blocks.table.weight")}</th>
</tr>
{/each}
</tbody>
</table>
</thead>
<tbody>
{#each blocks as block}
<tr>
<th><a href={`/block/${block.height}`}>{block.height}</a></th>
<td>
<span on:click={toggleTimestamps}>
<Time
live
relative={relativeTimestamps}
timestamp={block.timestamp}
/>
</span>
</td>
<td>{block.tx_count}</td>
<td>{block.bits}</td>
<td>{block.weight}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
<section>
@@ -95,27 +108,29 @@
<a class="header-size text-white hover:text-slate-400" href="/tx">โ</a>
</div>
<table>
<thead>
<tr>
<th>{$_("home.latest_txns.table.txn_id")}</th>
<th>{$_("home.latest_txns.table.value")}</th>
<th>{$_("home.latest_txns.table.size")}</th>
<th>{$_("home.latest_txns.table.fee")}</th>
</tr>
</thead>
<tbody>
{#each transactions as txn}
<div class="table-responsive">
<table>
<thead>
<tr>
<th><a href={`/tx/${txn.hash}`}><pre>{txn.hash}</pre></a></th>
<td>{txn.amount.value} {txn.amount.unit}</td>
<td>{txn.size.value} {txn.size.unit}</td>
<td>{txn.fee.value} {txn.fee.unit}</td>
<th>{$_("home.latest_txns.table.txn_id")}</th>
<th>{$_("home.latest_txns.table.value")}</th>
<th>{$_("home.latest_txns.table.size")}</th>
<th>{$_("home.latest_txns.table.fee")}</th>
</tr>
{/each}
</tbody>
</table>
</thead>
<tbody>
{#each transactions as txn}
<tr>
<th><a href={`/tx/${txn.hash}`}><pre>{txn.hash}</pre></a></th>
<td>{txn.amount.value} {txn.amount.unit}</td>
<td>{txn.size.value} {txn.size.unit}</td>
<td>{txn.fee.value} {txn.fee.unit}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
</div>
@@ -156,15 +156,19 @@
difficulty: block.difficulty,
transactions: transactions
.into_iter()
.map(|tx| Transaction {
hash: hex::encode(tx.hash),
version: tx.version,
lock_time: tx.lock_time,
weight: tx.weight,
coinbase: tx.coinbase,
replace_by_fee: tx.replace_by_fee,
inputs: tx.inputs.0.into_iter().map(Into::into).collect(),
outputs: tx.outputs.0.into_iter().map(Into::into).collect(),
.map(|mut tx| {
tx.hash.reverse();
Transaction {
hash: hex::encode(tx.hash),
version: tx.version,
lock_time: tx.lock_time,
weight: tx.weight,
coinbase: tx.coinbase,
replace_by_fee: tx.replace_by_fee,
inputs: tx.inputs.0.into_iter().map(Into::into).collect(),
outputs: tx.outputs.0.into_iter().map(Into::into).collect(),
}
})
.collect(),
};
@@ -40,10 +40,10 @@
<div>
<section class="p-7">
<h2 class="!p-0 !py-4">Block {block.height}</h2>
<p>{block.hash}</p>
<p class="break-all">{block.hash}</p>
</section>
<section>
<section class="table-responsive">
<table>
<tbody>
<tr>
@@ -64,12 +64,12 @@
{#each block.transactions as transaction}
<section class="p-4">
<h3 class="text-lg m-2" id={transaction.hash}>
<h3 class="text-base md:text-lg m-2 mb-4 md:mb-2 break-all" id={transaction.hash}>
<a href={`#${transaction.hash}`}>ยง</a>
{transaction.hash}
</h3>
<div class="table table-fixed w-full">
<div class="flex flex-col md:table table-fixed w-full">
<div class="table-cell break-all">
{#if transaction.coinbase}
<div class="item w-full">
@@ -78,33 +78,53 @@
{:else}
{#each transaction.inputs as input}
<div class="item w-full">
<div class="flex-grow">
<code>{input.previous_output?.address || briefHexToAsm(input.script).join('\n')}</code>
</div>
{#if input.previous_output}
<div class="amount">
<code>{(input.previous_output.value / scale).toFixed(8)} BTC</code>
<div class="item-inner">
<div class="flex-grow">
{#if input.previous_output?.address}
<a href="/address/{input.previous_output?.address}">
<code>{input.previous_output?.address}</code>
</a>
{:else}
<code>{briefHexToAsm(input.script).join('\n') || 'WITNESS (TODO)'}</code>
{/if}
</div>
{/if}
{#if input.previous_output}
<div class="amount">
<code>{(input.previous_output.value / scale).toFixed(8)} BTC</code>
</div>
{/if}
</div>
</div>
{/each}
{/if}
</div>
<div class="text-2xl table-cell w-10 align-middle text-center">
<div class="hidden md:table-cell text-2xl w-10 align-middle text-center">
โ
</div>
<div class="block md:hidden text-center text-2xl m-2 w-full align-middle text-center">
โ
</div>
<div class="table-cell break-all">
{#each transaction.outputs as output}
<div class="item w-full">
<div class="flex-grow">
<code>{output.address || briefHexToAsm(output.script).join(' ').trim() || output.script}</code>
</div>
<div class="item-inner">
<div class="flex-grow">
{#if output.address}
<a href="/address/{output.address}">
<code>{output.address}</code>
</a>
{:else}
<code>{briefHexToAsm(output.script).join(' ').trim() || output.script}</code>
{/if}
</div>
<div class="amount">
<code>{(output.value / scale).toFixed(8)} BTC</code>
<div class="amount">
<code>{(output.value / scale).toFixed(8)} BTC</code>
</div>
</div>
</div>
{/each}
@@ -147,11 +167,11 @@
}
.amount {
@apply whitespace-nowrap ml-4;
@apply whitespace-nowrap ml-0 mt-2 md:mt-0 md:ml-4;
}
div.item {
@apply bg-gray-900/40 p-4 rounded-lg flex mb-2;
@apply bg-gray-900/40 p-4 rounded-lg flex flex mb-2;
counter-increment: inout;
&:last-of-type {
@@ -161,6 +181,10 @@
&::before {
@apply inline-block w-6 mr-2 select-none text-zinc-500;
content: counter(inout);
}
.item-inner {
@apply flex flex-col md:flex-row w-full;
}
}
</style>