mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Implement routing, history state & spa config
This commit is contained in:
parent
96a250d21c
commit
c4c32dfa89
@ -1,6 +1,7 @@
|
||||
map $sent_http_content_type $expires {
|
||||
default off;
|
||||
text/css max;
|
||||
text/json max;
|
||||
application/javascript max;
|
||||
}
|
||||
|
||||
@ -14,15 +15,10 @@ server {
|
||||
|
||||
server_name client;
|
||||
|
||||
location = / {
|
||||
location ~* \.(html)$ {
|
||||
add_header Cache-Control 'no-cache';
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
expires $expires;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_cache bitfeed;
|
||||
proxy_cache_revalidate on;
|
||||
@ -42,6 +38,11 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires $expires;
|
||||
}
|
||||
}
|
||||
|
||||
upstream wsmonobackend {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public"
|
||||
"start": "sirv public --single"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
|
@ -2,9 +2,12 @@
|
||||
import TxViz from './components/TxViz.svelte'
|
||||
import analytics from './utils/analytics.js'
|
||||
import config from './config.js'
|
||||
import Router from './controllers/Router.js'
|
||||
import {settings} from './stores.js'
|
||||
|
||||
if (!$settings.noTrack && config.public) analytics.init()
|
||||
|
||||
const router = new Router(window.location.pathname)
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
@ -115,9 +115,9 @@
|
||||
async function explorePrevBlock (e) {
|
||||
e.preventDefault()
|
||||
if (!$loading && block) {
|
||||
$loading = true
|
||||
loading.increment()
|
||||
await searchBlockHeight(block.height - 1)
|
||||
$loading = false
|
||||
loading.decrement()
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,9 +125,9 @@
|
||||
e.preventDefault()
|
||||
if (!$loading && block) {
|
||||
if (block.height + 1 < $latestBlockHeight) {
|
||||
$loading = true
|
||||
loading.increment()
|
||||
await searchBlockHeight(block.height + 1)
|
||||
$loading = false
|
||||
loading.decrement()
|
||||
} else {
|
||||
dispatch('quitExploring')
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import clipboardIcon from '../assets/icon/cil-clipboard.svg'
|
||||
import twitterIcon from '../assets/icon/cib-twitter.svg'
|
||||
import { fade, fly } from 'svelte/transition'
|
||||
import { durationFormat } from '../utils/format.js'
|
||||
import { overlay, tiers } from '../stores.js'
|
||||
import { overlay, tiers, urlPath } from '../stores.js'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
let tab = 'form' // form | invoice | success
|
||||
@ -164,6 +164,7 @@ $: {
|
||||
}
|
||||
$: {
|
||||
if ($overlay === 'donation') {
|
||||
$urlPath = '/donate'
|
||||
startExpiryTimer()
|
||||
stopPollingInvoice()
|
||||
pollingEnabled = true
|
||||
@ -416,9 +417,13 @@ async function copyInvoice () {
|
||||
}
|
||||
analytics.trackEvent('donations', 'invoice', 'copy')
|
||||
}
|
||||
|
||||
function onClose () {
|
||||
$urlPath = "/"
|
||||
}
|
||||
</script>
|
||||
|
||||
<Overlay name="donation" fullSize>
|
||||
<Overlay name="donation" fullSize on:close={onClose}>
|
||||
<section class="donation-modal">
|
||||
<div class="tab-nav">
|
||||
<button class="to left" class:disabled={!canTabLeft} on:click={tabLeft}>←</button>
|
||||
|
@ -54,7 +54,7 @@ async function searchSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
||||
if (matchedQuery && matchedQuery.query !== 'address') {
|
||||
$loading++
|
||||
loading.increment()
|
||||
let searchErr
|
||||
switch(matchedQuery.query) {
|
||||
case 'txid':
|
||||
@ -79,7 +79,7 @@ async function searchSubmit (e) {
|
||||
}
|
||||
if (searchErr == null) errorMessage = null
|
||||
else handleSearchError(searchErr)
|
||||
$loading--
|
||||
loading.decrement()
|
||||
} else {
|
||||
errorMessage = 'enter a transaction id, block hash or block height'
|
||||
}
|
||||
|
@ -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, loading } from '../stores.js'
|
||||
import { exchangeRates, settings, sidebarToggle, newHighlightQuery, highlightingFull, detailTx, pageWidth, latestBlockHeight, highlightInOut, loading, urlPath } from '../stores.js'
|
||||
import { formatCurrency } from '../utils/fx.js'
|
||||
import { hlToHex, mixColor, teal, purple } from '../utils/color.js'
|
||||
import { SPKToAddress } from '../utils/encodings.js'
|
||||
@ -14,6 +14,7 @@ import { fade } from 'svelte/transition'
|
||||
function onClose () {
|
||||
$detailTx = null
|
||||
$highlightInOut = null
|
||||
$urlPath = "/"
|
||||
}
|
||||
|
||||
function formatBTC (sats) {
|
||||
@ -271,16 +272,16 @@ async function clickItem (item) {
|
||||
|
||||
async function goToInput(e, input) {
|
||||
e.preventDefault()
|
||||
$loading++
|
||||
loading.increment()
|
||||
await searchTx(input.prev_txid, null, input.prev_vout)
|
||||
$loading--
|
||||
loading.decrement()
|
||||
}
|
||||
|
||||
async function goToOutput(e, output) {
|
||||
e.preventDefault()
|
||||
$loading++
|
||||
loading.increment()
|
||||
await searchTx(output.spend.txid, output.spend.vin)
|
||||
$loading--
|
||||
loading.decrement()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
104
client/src/controllers/Router.js
Normal file
104
client/src/controllers/Router.js
Normal file
@ -0,0 +1,104 @@
|
||||
import { urlPath, settings, loading, detailTx, highlightInOut, explorerBlockData, overlay } from '../stores.js'
|
||||
import { searchTx, searchBlockHash, searchBlockHeight } from '../utils/search.js'
|
||||
|
||||
export default class Router {
|
||||
constructor (initialPath = '/') {
|
||||
this.path = initialPath
|
||||
this.apply(initialPath)
|
||||
urlPath.subscribe(val => {
|
||||
if (val != null) {
|
||||
this.pushHistory(val)
|
||||
this.path = val
|
||||
}
|
||||
})
|
||||
window.addEventListener('popstate', e => {
|
||||
if (e && e.state && e.state.path) {
|
||||
this.path = e.state.path
|
||||
this.apply(e.state.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pushHistory (path, replace = false) {
|
||||
if (replace) {
|
||||
window.history.replaceState({path}, "", path, window.history.state)
|
||||
} else if (path !== this.path) {
|
||||
window.history.pushState({path}, "", path)
|
||||
}
|
||||
}
|
||||
|
||||
clearPath () {
|
||||
urlPath.set("/")
|
||||
}
|
||||
|
||||
apply (path) {
|
||||
const parts = path.split("/")
|
||||
if (path === '/') {
|
||||
detailTx.set(null)
|
||||
highlightInOut.set(null)
|
||||
urlPath.set("/")
|
||||
explorerBlockData.set(null)
|
||||
overlay.set(null)
|
||||
} else {
|
||||
switch (parts[1]) {
|
||||
case 'block':
|
||||
if (parts[2] === "height") {
|
||||
try {
|
||||
const height = parseInt(parts[3])
|
||||
this.goToBlockHeight(height)
|
||||
} catch (err) {
|
||||
// ??
|
||||
}
|
||||
} else if (parts[2]) {
|
||||
this.goToBlock(parts[2])
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tx':
|
||||
if (parts[2]) {
|
||||
this.goToTransaction(parts[2])
|
||||
}
|
||||
break;
|
||||
|
||||
case 'donate':
|
||||
overlay.set('donation')
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async goToBlock (blockhash) {
|
||||
loading.increment()
|
||||
await searchBlockHash(blockhash)
|
||||
loading.decrement()
|
||||
}
|
||||
|
||||
async goToBlockHeight (height) {
|
||||
loading.increment()
|
||||
await searchBlockHeight(height)
|
||||
loading.decrement()
|
||||
}
|
||||
|
||||
async goToTransaction (q) {
|
||||
loading.increment()
|
||||
const parts = q.split(":")
|
||||
let txid, input, output
|
||||
if (parts.length) {
|
||||
if (parts[0].length == 64) {
|
||||
txid = parts[0]
|
||||
output = parseInt(parts[1])
|
||||
if (isNaN(output)) output = null
|
||||
} else if (parts[1].length == 64) {
|
||||
txid = parts[1]
|
||||
input = parseInt(parts[0])
|
||||
if (isNaN(input)) input = null
|
||||
} else {
|
||||
// invalid
|
||||
}
|
||||
} else {
|
||||
txid = q
|
||||
}
|
||||
await searchTx(txid, input, output)
|
||||
loading.decrement()
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import BitcoinBlock from '../models/BitcoinBlock.js'
|
||||
import TxSprite from '../models/TxSprite.js'
|
||||
import { FastVertexArray } from '../utils/memory.js'
|
||||
import { searchTx, fetchSpends, addSpends } from '../utils/search.js'
|
||||
import { overlay, txCount, mempoolCount, mempoolScreenHeight, blockVisible, currentBlock, selectedTx, detailTx, blockAreaSize, highlight, colorMode, blocksEnabled, latestBlockHeight, explorerBlockData, blockTransitionDirection, loading } from '../stores.js'
|
||||
import { overlay, txCount, mempoolCount, mempoolScreenHeight, blockVisible, currentBlock, selectedTx, detailTx, blockAreaSize, highlight, colorMode, blocksEnabled, latestBlockHeight, explorerBlockData, blockTransitionDirection, loading, urlPath } from '../stores.js'
|
||||
import config from "../config.js"
|
||||
|
||||
export default class TxController {
|
||||
@ -322,6 +322,7 @@ export default class TxController {
|
||||
prevBlockScene.expire(2000)
|
||||
this.explorerBlockScene = null
|
||||
this.explorerBlock = null
|
||||
urlPath.set("/")
|
||||
}
|
||||
if (this.blockScene && this.block) {
|
||||
blockTransitionDirection.set('right')
|
||||
@ -397,6 +398,7 @@ export default class TxController {
|
||||
} else {
|
||||
const spendResult = await fetchSpends(selected.id)
|
||||
if (spendResult) selected = addSpends(selected, spendResult)
|
||||
urlPath.set(`/tx/${selected.id}`)
|
||||
detailTx.set(selected)
|
||||
overlay.set('tx')
|
||||
}
|
||||
|
@ -170,3 +170,5 @@ export const highlightInOut = writable(null)
|
||||
export const loading = createCounter()
|
||||
export const explorerBlockData = writable(null)
|
||||
export const blockTransitionDirection = writable(null)
|
||||
|
||||
export const urlPath = writable(null)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import api from './api.js'
|
||||
import BitcoinTx from '../models/BitcoinTx.js'
|
||||
import BitcoinBlock from '../models/BitcoinBlock.js'
|
||||
import { detailTx, selectedTx, currentBlock, explorerBlockData, overlay, highlightInOut } from '../stores.js'
|
||||
import { detailTx, selectedTx, currentBlock, explorerBlockData, overlay, highlightInOut, urlPath } from '../stores.js'
|
||||
import { addressToSPK } from './encodings.js'
|
||||
|
||||
// Quick heuristic matching to guess what kind of search a query is for
|
||||
@ -198,6 +198,13 @@ function addSpends(tx, spends) {
|
||||
export {addSpends as addSpends}
|
||||
|
||||
export async function searchTx (txid, input, output) {
|
||||
if (input != null) {
|
||||
urlPath.set(`/tx/${input}:${txid}`)
|
||||
} else if (output != null) {
|
||||
urlPath.set(`/tx/${txid}:${output}`)
|
||||
} else {
|
||||
urlPath.set(`/tx/${txid}`)
|
||||
}
|
||||
try {
|
||||
let searchResult = await fetchTx(txid)
|
||||
const spendResult = await fetchSpends(txid)
|
||||
@ -218,6 +225,7 @@ export async function searchTx (txid, input, output) {
|
||||
}
|
||||
|
||||
export async function searchBlockHash (hash) {
|
||||
urlPath.set(`/block/${hash}`)
|
||||
try {
|
||||
const searchResult = await fetchBlockByHash(hash)
|
||||
if (searchResult) {
|
||||
@ -235,6 +243,7 @@ export async function searchBlockHash (hash) {
|
||||
}
|
||||
|
||||
export async function searchBlockHeight (height) {
|
||||
urlPath.set(`/block/height/${height}`)
|
||||
try {
|
||||
const searchResult = await fetchBlockByHeight(height)
|
||||
if (searchResult) {
|
||||
|
@ -85,6 +85,7 @@ defmodule BitcoinStream.Index.Spend do
|
||||
|
||||
@impl true
|
||||
def handle_cast({:block_disconnected, hash}, [dbref, indexed, done]) do
|
||||
Logger.info("block disconnected: #{hash}");
|
||||
if (indexed != nil and done) do
|
||||
block_disconnected(dbref, hash)
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user