🏡 index : ~doyle/blocks.ls.git

author Jordan Doyle <jordan@doyle.la> 2022-05-21 18:04:55.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-05-21 18:04:55.0 +00:00:00
commit
b109111e78790ee10dddbca43f6bfc9f3bf712db [patch]
tree
750a91e05527b621e3b27b13a1fe031d13ae8d3a
parent
1eb91d8e9cac8f54b4c6a60d2be6ed1cbcde5240
download
b109111e78790ee10dddbca43f6bfc9f3bf712db.tar.gz

Responsive tables on index and block view



Diff

 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/block/[id].svelte | 64 ++++++++++++++++--------
 frontend/src/routes/index.svelte      | 97 ++++++++++++++++++++----------------
 package-lock.json                     |  6 +--
 web-api/src/methods/block.rs          | 22 ++++----
 11 files changed, 179 insertions(+), 79 deletions(-)

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2a0987e..c72fc46 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -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"
      },
diff --git a/frontend/package.json b/frontend/package.json
index b52575c..d43546e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -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"
  }
}
diff --git a/frontend/src/_table.scss b/frontend/src/_table.scss
index f664391..454543f 100644
--- a/frontend/src/_table.scss
+++ b/frontend/src/_table.scss
@@ -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;

diff --git a/frontend/src/lib/Time.svelte b/frontend/src/lib/Time.svelte
new file mode 100644
index 0000000..3d99465
--- /dev/null
+++ b/frontend/src/lib/Time.svelte
@@ -0,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>
diff --git a/frontend/src/lib/bitcoinScript.ts b/frontend/src/lib/bitcoinScript.ts
index 8f8adb5..0a73125 100644
--- a/frontend/src/lib/bitcoinScript.ts
+++ b/frontend/src/lib/bitcoinScript.ts
@@ -20,8 +20,10 @@ export function hexToAsm(hex: string) {
        }

        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))
diff --git a/frontend/src/lib/dayjs.ts b/frontend/src/lib/dayjs.ts
new file mode 100644
index 0000000..82466c0
--- /dev/null
+++ b/frontend/src/lib/dayjs.ts
@@ -0,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 };
diff --git a/frontend/src/routes/__layout.svelte b/frontend/src/routes/__layout.svelte
index aa16b20..a0aadb4 100644
--- a/frontend/src/routes/__layout.svelte
+++ b/frontend/src/routes/__layout.svelte
@@ -36,7 +36,9 @@
  </div>
</header>

<slot />
<main>
  <slot />
</main>

<style lang="scss">
  header {
@@ -65,4 +67,8 @@
      }
    }
  }

  main {
    @apply mx-5;
  }
</style>
diff --git a/frontend/src/routes/block/[id].svelte b/frontend/src/routes/block/[id].svelte
index 2daf05f..3d0474b 100644
--- a/frontend/src/routes/block/[id].svelte
+++ b/frontend/src/routes/block/[id].svelte
@@ -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 {
@@ -162,5 +182,9 @@
      @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>
diff --git a/frontend/src/routes/index.svelte b/frontend/src/routes/index.svelte
index ffe2de5..a4eb572 100644
--- a/frontend/src/routes/index.svelte
+++ b/frontend/src/routes/index.svelte
@@ -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> <!-- todo: moment.js -->
            <td>{block.tx_count}</td>
            <td>{block.bits}</td> <!-- todo: this isn't really size -->
            <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> <!-- todo: this isn't really size -->
              <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>

diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 320b9bc..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,6 +0,0 @@
{
  "name": "list-blocks",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {}
}
diff --git a/web-api/src/methods/block.rs b/web-api/src/methods/block.rs
index 492f306..098a9c6 100644
--- a/web-api/src/methods/block.rs
+++ b/web-api/src/methods/block.rs
@@ -156,15 +156,19 @@ pub async fn handle(
        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(),
    };