mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Mobile & caching fixes
This commit is contained in:
parent
a8a945e77e
commit
d386f01586
@ -31,7 +31,7 @@
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/global.css?v=2.3.0'>
|
||||
<link rel='stylesheet' href='/build/bundle.css'>
|
||||
<script src="/env.js"></script>
|
||||
<script defer src="/build/bundle.js"></script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Icon from '../components/Icon.svelte'
|
||||
import closeIcon from '../assets/icon/cil-x-circle.svg'
|
||||
import { shortBtcFormat, longBtcFormat, dateFormat, numberFormat } from '../utils/format.js'
|
||||
import { exchangeRates, settings, blocksEnabled, latestBlockHeight, blockTransitionDirection, loading } from '../stores.js'
|
||||
import { exchangeRates, settings, blocksEnabled, latestBlockHeight, blockTransitionDirection, loading, freezeResize, pageWidth, pageHeight } from '../stores.js'
|
||||
import { formatCurrency } from '../utils/fx.js'
|
||||
import { searchBlockHeight } from '../utils/search.js'
|
||||
|
||||
@ -20,6 +20,18 @@
|
||||
let restoring = false
|
||||
let formattedBlockValue = ''
|
||||
|
||||
let compactView
|
||||
let landscape
|
||||
let tinyView
|
||||
$: {
|
||||
if ($pageWidth && $pageHeight && !$freezeResize) {
|
||||
const aspectRatio = ($pageWidth / $pageHeight)
|
||||
landscape = aspectRatio >= 1
|
||||
compactView = (aspectRatio < 1) && ($pageHeight < 760)
|
||||
tinyView = (aspectRatio < 1) && ($pageHeight <= 400)
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (block && block.value) {
|
||||
const rate = $exchangeRates[$settings.currency]
|
||||
@ -188,7 +200,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 1/1) and (max-height: 760px) {
|
||||
&.compact {
|
||||
.compact {
|
||||
display: block;
|
||||
}
|
||||
@ -196,14 +208,6 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (aspect-ratio: 1/1) and (max-height: 760px) {
|
||||
.compact {
|
||||
display: none;
|
||||
}
|
||||
.full-size {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
@ -278,65 +282,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-aspect-ratio: 1/1) {
|
||||
.block-info {
|
||||
bottom: unset;
|
||||
left: unset;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
padding-right: .5rem;
|
||||
.block-info.landscape {
|
||||
bottom: unset;
|
||||
left: unset;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
padding-right: .5rem;
|
||||
|
||||
min-width: 0;
|
||||
transform: translateX(0);
|
||||
min-width: 0;
|
||||
transform: translateX(0);
|
||||
|
||||
.data-row {
|
||||
.data-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
|
||||
&.spacer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
|
||||
&.spacer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.data-field {
|
||||
white-space: wrap;
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
|
||||
&.title-field {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
&.close-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.standalone.close-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 100%;
|
||||
margin: 5px;
|
||||
.data-field {
|
||||
white-space: wrap;
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
|
||||
&.title-field {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
&.close-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-aspect-ratio: 1/1) and (max-height: 400px) {
|
||||
.standalone.close-button {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
margin-top: 0;
|
||||
}
|
||||
.standalone.landscape.close-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 100%;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.standalone.tinyscreen.close-button {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#key transitionDirection}
|
||||
{#each ((block != null && visible && $blocksEnabled) ? [block] : []) as block (block.id)}
|
||||
<div class="block-info-container" out:fly|local={flyOut} in:fly|local={flyIn}>
|
||||
<div class="block-info">
|
||||
<div class="block-info" class:compact={compactView} class:landscape={landscape}>
|
||||
<!-- <span class="data-field">Hash: { block.id }</span> -->
|
||||
<div class="full-size">
|
||||
<div class="data-row">
|
||||
@ -363,7 +363,7 @@
|
||||
</div>
|
||||
<div class="compact">
|
||||
<div class="data-row">
|
||||
<span class="data-field title-field" title="{block.miner_sig}"><b>Latest Block: </b>{ numberFormat.format(block.height) }</span>
|
||||
<span class="data-field title-field" title="{block.miner_sig}"><b>{#if block.height == $latestBlockHeight}Latest {/if}Block: </b>{ numberFormat.format(block.height) }</span>
|
||||
<button class="data-field close-button" on:click={hideBlock}><Icon icon={closeIcon} color="var(--palette-x)" /></button>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
@ -371,7 +371,7 @@
|
||||
<span class="data-field">{ formattedBlockValue }</span>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-field">{ formatCount(block.txnCount) } transactions</span>
|
||||
<span class="data-field">{ formatCount(block.txnCount) } transaction{block.txnCount == 1 ? '' : 's'}</span>
|
||||
{#if block.fees != null}
|
||||
<span class="data-field">{ formatFee(block.avgFeerate) } sats/vb</span>
|
||||
{:else}
|
||||
@ -395,7 +395,7 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
<button class="close-button standalone" on:click={hideBlock}>
|
||||
<button class="close-button standalone" class:landscape={landscape} class:tinyscreen={tinyView} on:click={hideBlock}>
|
||||
<Icon icon={closeIcon} color="var(--palette-x)" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ import TxIcon from '../assets/icon/cil-arrow-circle-right.svg'
|
||||
import BlockIcon from '../assets/icon/grid-icon.svg'
|
||||
import { fly } from 'svelte/transition'
|
||||
import { matchQuery, searchTx, searchBlockHeight, searchBlockHash } from '../utils/search.js'
|
||||
import { selectedTx, detailTx, overlay, loading } from '../stores.js'
|
||||
import { selectedTx, detailTx, overlay, loading, freezeResize } from '../stores.js'
|
||||
|
||||
const queryIcons = {
|
||||
txid: TxIcon,
|
||||
@ -53,6 +53,8 @@ function handleSearchError (err) {
|
||||
async function searchSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
||||
if (document.activeElement) document.activeElement.blur()
|
||||
|
||||
if (matchedQuery && matchedQuery.query !== 'address') {
|
||||
loading.increment()
|
||||
let searchErr
|
||||
@ -87,6 +89,17 @@ async function searchSubmit (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
let freezeTimeout
|
||||
function focusIn(e) {
|
||||
if (freezeTimeout) clearTimeout(freezeTimeout)
|
||||
$freezeResize = true
|
||||
}
|
||||
async function focusOut(e) {
|
||||
freezeTimeout = setTimeout(() => {
|
||||
$freezeResize = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style type="text/scss">
|
||||
@ -219,7 +232,7 @@ async function searchSubmit (e) {
|
||||
|
||||
<div class="input-wrapper" transition:fly={{ y: -25 }}>
|
||||
<form class="search-form" action="" on:submit={searchSubmit}>
|
||||
<input class="search-input" type="text" bind:value={query} placeholder="txid, block id or block height">
|
||||
<input class="search-input" type="text" bind:value={query} placeholder="txid, block id or block height" on:focusin={focusIn} on:focusout={focusOut}>
|
||||
<div class="clear-button" class:disabled={query == null || query === ''} on:click={clearInput} title="Clear">
|
||||
<Icon icon={CrossIcon}/>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ import CrossIcon from '../assets/icon/cil-x.svg'
|
||||
import AddressIcon from '../assets/icon/cil-wallet.svg'
|
||||
import TxIcon from '../assets/icon/cil-arrow-circle-right.svg'
|
||||
import { matchQuery } from '../utils/search.js'
|
||||
import { highlight, newHighlightQuery, highlightingFull } from '../stores.js'
|
||||
import { highlight, newHighlightQuery, highlightingFull, freezeResize } from '../stores.js'
|
||||
import { hlToHex, highlightA, highlightB, highlightC, highlightD, highlightE } from '../utils/color.js'
|
||||
|
||||
const highlightColors = [highlightA, highlightB, highlightC, highlightD, highlightE]
|
||||
@ -145,9 +145,21 @@ async function remove (index) {
|
||||
|
||||
function searchSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (document.activeElement) document.activeElement.blur()
|
||||
add()
|
||||
return false
|
||||
}
|
||||
|
||||
let freezeTimeout
|
||||
function focusIn(e) {
|
||||
if (freezeTimeout) clearTimeout(freezeTimeout)
|
||||
$freezeResize = true
|
||||
}
|
||||
async function focusOut(e) {
|
||||
freezeTimeout = setTimeout(() => {
|
||||
$freezeResize = false
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/scss">
|
||||
@ -244,7 +256,7 @@ function searchSubmit (e) {
|
||||
<div class="input-wrapper" class:full={$highlightingFull} style="--input-color: {queryColorHex};">
|
||||
{#if !$highlightingFull }
|
||||
<form class="search-form" action="" on:submit={searchSubmit}>
|
||||
<input class="search-input" type="text" bind:value={query} placeholder="Enter an address or txid...">
|
||||
<input class="search-input" type="text" bind:value={query} placeholder="Enter an address or txid..." on:focusin={focusIn} on:focusOut={focusOut}>
|
||||
<button type="submit" class="search-submit" />
|
||||
</form>
|
||||
{:else}
|
||||
|
@ -20,7 +20,7 @@ import MempoolLegend from '../components/MempoolLegend.svelte'
|
||||
import ContactTab from '../components/ContactTab.svelte'
|
||||
import SearchTab from '../components/SearchTab.svelte'
|
||||
|
||||
import { sidebarToggle, overlay, currentBlock, blockVisible, haveSupporters } from '../stores.js'
|
||||
import { sidebarToggle, overlay, currentBlock, blockVisible, haveSupporters, freezeResize } from '../stores.js'
|
||||
|
||||
let searchTabComponent
|
||||
|
||||
@ -58,12 +58,14 @@ function showBlock () {
|
||||
align-items: flex-start;
|
||||
|
||||
@media (max-width: 480px) and (max-height: 480px) {
|
||||
display: none;
|
||||
&:not(.frozen) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar" class:frozen={$freezeResize}>
|
||||
<!-- displayed in reverse order, to preserve proper z-index layering -->
|
||||
{#if blockHidden }
|
||||
<SidebarTab on:click={() => showBlock()} tooltip="Show Latest Block">
|
||||
|
@ -1,8 +1,10 @@
|
||||
<script>
|
||||
import { tick } from 'svelte'
|
||||
import Toggle from './util/Toggle.svelte'
|
||||
import Pill from './util/Pill.svelte'
|
||||
import Select from 'svelte-select'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { freezeResize } from '../stores.js'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = false
|
||||
@ -26,6 +28,18 @@ function filterSelectItems (label, filterText, option) {
|
||||
function onSelect (e) {
|
||||
selectedOption = e.detail
|
||||
dispatch('input', selectedOption.value )
|
||||
if (document.activeElement) document.activeElement.blur()
|
||||
}
|
||||
|
||||
let freezeTimeout
|
||||
function focusIn(e) {
|
||||
if (freezeTimeout) clearTimeout(freezeTimeout)
|
||||
$freezeResize = true
|
||||
}
|
||||
async function focusOut(e) {
|
||||
freezeTimeout = setTimeout(() => {
|
||||
$freezeResize = false
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -73,7 +87,7 @@ function onSelect (e) {
|
||||
<span class="label">{ label }</span>
|
||||
<Pill active={value} left={falseLabel} right={trueLabel} />
|
||||
{:else if type === 'dropdown'}
|
||||
<div class="select">
|
||||
<div class="select" on:focusin={focusIn} on:focusOut={focusOut}>
|
||||
<Select items={ options } value={selectedOption} isSearchable={true} isClearable={false} itemFilter={filterSelectItems} placeholder={label} on:select={onSelect} showIndicator={true} />
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import fragShaderSrc from '../shaders/tx.frag'
|
||||
import TxSprite from '../models/TxSprite.js'
|
||||
import { color, hcl } from 'd3-color'
|
||||
import { darkMode, frameRate, avgFrameRate, nativeAntialias, settings, devSettings } from '../stores.js'
|
||||
import { darkMode, frameRate, avgFrameRate, nativeAntialias, settings, devSettings, freezeResize } from '../stores.js'
|
||||
import config from '../config.js'
|
||||
|
||||
let canvas
|
||||
@ -32,6 +32,11 @@
|
||||
export let controller
|
||||
export let running = false
|
||||
|
||||
let sizeFrozen
|
||||
freezeResize.subscribe(val => {
|
||||
sizeFrozen = val
|
||||
})
|
||||
|
||||
// Shader attributes
|
||||
// each attribute (except index) contains [x: startValue, y: endValue, z: startTime, w: rate]
|
||||
// shader interpolates between start and end values at the given rate, from the given time
|
||||
@ -72,9 +77,12 @@
|
||||
resizeCanvas()
|
||||
}
|
||||
|
||||
let resizeTimer
|
||||
function resizeCanvas () {
|
||||
if (resizeTimer) clearTimeout(resizeTimer)
|
||||
resizeTimer = null
|
||||
// var rect = canvas.parentNode.getBoundingClientRect()
|
||||
if (canvas) {
|
||||
if (canvas && !sizeFrozen) {
|
||||
displayWidth = window.innerWidth
|
||||
displayHeight = window.innerHeight
|
||||
if (simulateAntialiasing) {
|
||||
@ -86,7 +94,7 @@
|
||||
}
|
||||
if (gl) gl.viewport(0, 0, canvas.width, canvas.height)
|
||||
} else {
|
||||
setTimeout(resizeCanvas, 500)
|
||||
resizeTimer = setTimeout(resizeCanvas, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
import { settings, overlay, serverConnected, serverDelay, txCount, mempoolCount,
|
||||
mempoolScreenHeight, frameRate, avgFrameRate, blockVisible, tinyScreen,
|
||||
compactScreen, currentBlock, latestBlockHeight, selectedTx, blockAreaSize,
|
||||
devEvents, devSettings, pageWidth, pageHeight, loading } from '../stores.js'
|
||||
devEvents, devSettings, pageWidth, pageHeight, loading, freezeResize } from '../stores.js'
|
||||
import BlockInfo from '../components/BlockInfo.svelte'
|
||||
import SearchBar from '../components/SearchBar.svelte'
|
||||
import TxInfo from '../components/TxInfo.svelte'
|
||||
@ -53,6 +53,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
let canvasWidth = '100%'
|
||||
let canvasHeight = '100%'
|
||||
$: {
|
||||
if ($freezeResize) {
|
||||
canvasWidth = `${window.innerWidth}px`
|
||||
canvasHeight = `${window.innerHeight}px`
|
||||
} else {
|
||||
canvasWidth = '100%'
|
||||
canvasHeight = '100%'
|
||||
resize()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
txController = new TxController({ width, height })
|
||||
|
||||
@ -86,7 +99,7 @@
|
||||
function resize () {
|
||||
$pageWidth = window.innerWidth
|
||||
$pageHeight = window.innerHeight
|
||||
if (width !== window.innerWidth - 20 || height !== window.innerHeight - 20) {
|
||||
if ((width !== window.innerWidth - 20 || height !== window.innerHeight - 20) && !$freezeResize) {
|
||||
// don't force resize unless the viewport has actually changed
|
||||
width = window.innerWidth - 20
|
||||
height = window.innerHeight - 20
|
||||
@ -483,7 +496,7 @@
|
||||
<svelte:window on:resize={resize} on:load={resize} on:click={pointerLeave} />
|
||||
<!-- <svelte:window on:resize={resize} on:click={pointerMove} /> -->
|
||||
|
||||
<div class="tx-area" class:light-mode={!$settings.darkMode}>
|
||||
<div class="tx-area" class:light-mode={!$settings.darkMode} style="width: {canvasWidth}; height: {canvasHeight}">
|
||||
<div class="canvas-wrapper" on:pointerleave={pointerLeave} on:pointermove={pointerMove} on:click={onClick}>
|
||||
<TxRender controller={txController} />
|
||||
|
||||
@ -539,13 +552,15 @@
|
||||
<SearchBar />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="alert-bar-wrapper">
|
||||
{#if config.messagesEnabled && $settings.showMessages && !$tinyScreen }
|
||||
<Alerts />
|
||||
{:else}
|
||||
<div class="spacer"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !$tinyScreen}
|
||||
<div class="alert-bar-wrapper">
|
||||
{#if config.messagesEnabled && $settings.showMessages}
|
||||
<Alerts />
|
||||
{:else}
|
||||
<div class="spacer"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Sidebar />
|
||||
|
@ -152,13 +152,24 @@ export const highlightingFull = writable(false)
|
||||
|
||||
export const pageWidth = writable(window.innerWidth)
|
||||
export const pageHeight = writable(window.innerHeight)
|
||||
export const freezeResize = writable(false)
|
||||
|
||||
export const tinyScreen = derived([pageWidth, pageHeight], ([$pageWidth, $pageHeight]) => {
|
||||
const aspectRatio = $pageWidth / $pageHeight
|
||||
return (aspectRatio >= 1 && pageWidth < 480) || (aspectRatio <= 1 && $pageHeight < 480)
|
||||
let lastTinyScreen
|
||||
export const tinyScreen = derived([pageWidth, pageHeight, freezeResize], ([$pageWidth, $pageHeight, $freezeResize]) => {
|
||||
if ($freezeResize) return lastTinyScreen
|
||||
else {
|
||||
const aspectRatio = $pageWidth / $pageHeight
|
||||
lastTinyScreen = (aspectRatio >= 1 && $pageWidth < 480) || (aspectRatio <= 1 && $pageHeight < 480)
|
||||
return lastTinyScreen
|
||||
}
|
||||
})
|
||||
export const compactScreen = derived([pageWidth, pageHeight], ([$pageWidth, $pageHeight]) => {
|
||||
return ($pageWidth <= 640 && $pageHeight <= 550)
|
||||
let lastCompactScreen
|
||||
export const compactScreen = derived([pageWidth, pageHeight, freezeResize], ([$pageWidth, $pageHeight, $freezeResize]) => {
|
||||
if ($freezeResize) return lastTinyScreen
|
||||
else {
|
||||
lastCompactScreen = ($pageWidth <= 640 && $pageHeight <= 550)
|
||||
return lastCompactScreen
|
||||
}
|
||||
})
|
||||
|
||||
export const blocksEnabled = derived([settings], ([$settings]) => {
|
||||
|
@ -34,7 +34,7 @@ defmodule BitcoinStream.Router do
|
||||
match "/api/block/:hash" do
|
||||
case get_block(hash) do
|
||||
{:ok, block, true} ->
|
||||
put_resp_header(conn, "cache-control", "public, max-age=120, immutable")
|
||||
put_resp_header(conn, "cache-control", "public, max-age=1200, immutable")
|
||||
|> send_resp(200, block)
|
||||
|
||||
{:ok, block, false} ->
|
||||
|
Loading…
Reference in New Issue
Block a user