mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
242 lines
5.2 KiB
Svelte
242 lines
5.2 KiB
Svelte
<script>
|
|
import { tick } from 'svelte'
|
|
import { fade } from 'svelte/transition'
|
|
import { flip } from 'svelte/animate'
|
|
import Icon from './Icon.svelte'
|
|
import SearchIcon from '../assets/icon/cil-search.svg'
|
|
import CrossIcon from '../assets/icon/cil-x-circle.svg'
|
|
import AddressIcon from '../assets/icon/cil-wallet.svg'
|
|
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'
|
|
|
|
const queryIcons = {
|
|
txid: TxIcon,
|
|
input: TxIcon,
|
|
output: TxIcon,
|
|
// address: AddressIcon,
|
|
blockhash: BlockIcon,
|
|
blockheight: BlockIcon,
|
|
}
|
|
|
|
let query
|
|
let matchedQuery
|
|
let errorMessage
|
|
|
|
$: {
|
|
if (query) {
|
|
matchedQuery = matchQuery(query)
|
|
} else {
|
|
matchedQuery = null
|
|
}
|
|
errorMessage = null
|
|
}
|
|
|
|
function clearInput () {
|
|
query = null
|
|
}
|
|
|
|
function handleSearchError (err) {
|
|
switch (err) {
|
|
case '404':
|
|
if (matchedQuery && matchedQuery.label) {
|
|
errorMessage = `${matchedQuery.label} not found`
|
|
}
|
|
break;
|
|
default:
|
|
errorMessage = 'server error'
|
|
}
|
|
}
|
|
|
|
async function searchSubmit (e) {
|
|
e.preventDefault()
|
|
|
|
if (matchedQuery && matchedQuery.query !== 'address') {
|
|
loading.increment()
|
|
let searchErr
|
|
switch(matchedQuery.query) {
|
|
case 'txid':
|
|
searchErr = await searchTx(matchedQuery.txid)
|
|
break;
|
|
|
|
case 'input':
|
|
searchErr = await searchTx(matchedQuery.txid, matchedQuery.input, null)
|
|
break;
|
|
|
|
case 'output':
|
|
searchErr = await searchTx(matchedQuery.txid, null, matchedQuery.output)
|
|
break;
|
|
|
|
case 'blockheight':
|
|
searchErr = await searchBlockHeight(matchedQuery.height)
|
|
break;
|
|
|
|
case 'blockhash':
|
|
searchErr = await searchBlockHash(matchedQuery.hash)
|
|
break;
|
|
}
|
|
if (searchErr == null) errorMessage = null
|
|
else handleSearchError(searchErr)
|
|
loading.decrement()
|
|
} else {
|
|
errorMessage = 'enter a transaction id, block hash or block height'
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
</script>
|
|
|
|
<style type="text/scss">
|
|
.input-wrapper {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: baseline;
|
|
--input-color: var(--palette-x);
|
|
width: 100%;
|
|
max-width: 600px;
|
|
margin: 0 1em;
|
|
|
|
.clear-button {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: .4em;
|
|
margin: 0;
|
|
color: var(--palette-bad);
|
|
font-size: 1.2em;
|
|
cursor: pointer;
|
|
opacity: 1;
|
|
transition: opacity 300ms;
|
|
|
|
&.disabled {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.input-icon {
|
|
font-size: 24px;
|
|
margin: 0 10px;
|
|
transition: opacity 300ms, color 300ms, background 300ms;
|
|
color: var(--input-color);
|
|
|
|
&.search {
|
|
color: var(--bold-a);
|
|
}
|
|
&.icon-button {
|
|
cursor: pointer;
|
|
}
|
|
|
|
&.hidden {
|
|
opacity: 0;
|
|
}
|
|
|
|
&.icon-button {
|
|
background: var(--palette-d);
|
|
padding: 6px;
|
|
border-radius: 5px;
|
|
&:hover {
|
|
background: var(--palette-e);
|
|
}
|
|
|
|
&.disabled {
|
|
color: var(--palette-e);
|
|
background: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.search-form {
|
|
position: relative;
|
|
width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
|
|
.search-submit {
|
|
display: none;
|
|
}
|
|
|
|
.underline {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
height: 2px;
|
|
background: var(--palette-x);
|
|
opacity: 0.5;
|
|
|
|
&.active {
|
|
width: 0%;
|
|
opacity: 1;
|
|
transition: width 300ms;
|
|
}
|
|
}
|
|
|
|
.error-msg {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 100%;
|
|
margin: 0;
|
|
font-size: 0.9em;
|
|
color: var(--palette-bad);
|
|
}
|
|
|
|
.input-icon.query-type {
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: .4em;
|
|
margin: 0;
|
|
color: var(--palette-x);
|
|
font-size: 1.2em;
|
|
}
|
|
}
|
|
|
|
.search-input:active, .search-input:focus {
|
|
& ~ .underline.active {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.search-input {
|
|
background: none;
|
|
border: none;
|
|
outline: none;
|
|
margin: 0;
|
|
color: var(--input-color);
|
|
width: 100%;
|
|
padding-left: 1.5em;
|
|
padding-right: 1.5em;
|
|
|
|
&.disabled {
|
|
color: var(--palette-e);
|
|
user-select: none;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<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="Enter a txid">
|
|
<div class="clear-button" class:disabled={query == null || query === ''} on:click={clearInput} title="Clear">
|
|
<Icon icon={CrossIcon}/>
|
|
</div>
|
|
<div class="underline" />
|
|
<div class="underline active" />
|
|
<button type="submit" class="search-submit" />
|
|
{#if matchedQuery && matchedQuery.query && queryIcons[matchedQuery.query]}
|
|
<div class="input-icon query-type" transition:fade={{ duration: 300 }} title={matchedQuery.label}>
|
|
<Icon icon={queryIcons[matchedQuery.query]} />
|
|
</div>
|
|
{/if}
|
|
{#if errorMessage }
|
|
<p class="error-msg" transition:fade={{ duration: 300 }}>{ errorMessage }</p>
|
|
{/if}
|
|
</form>
|
|
<div class="input-icon search icon-button" on:click={searchSubmit} title="Search">
|
|
<Icon icon={SearchIcon}/>
|
|
</div>
|
|
</div>
|