Display amounts in secondary currency in explorer

This commit is contained in:
Martin Boehm 2022-11-18 19:51:25 +01:00 committed by Martin
parent 715567d7f3
commit 86743cfca5
6 changed files with 780 additions and 801 deletions

View File

@ -30,6 +30,8 @@ const blocksOnPage = 50
const mempoolTxsOnPage = 50
const txsInAPI = 1000
const secondaryCoinCookieName = "secondary_coin"
const (
_ = iota
apiV1
@ -330,8 +332,8 @@ func (s *PublicServer) jsonHandler(handler func(r *http.Request, apiVersion int)
}
}
func (s *PublicServer) newTemplateData() *TemplateData {
return &TemplateData{
func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
t := &TemplateData{
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
CoinLabel: s.is.CoinLabel,
@ -339,10 +341,47 @@ func (s *PublicServer) newTemplateData() *TemplateData {
InternalExplorer: s.internalExplorer && !s.is.InitialSync,
TOSLink: api.Text.TOSLink,
}
if s.is.HasFiatRates {
// get the secondary coin and if it should be shown either from query parameters "secondary" and "use_secondary"
// or from the cookie "secondary_coin" in the format secondary=use_secondary, for example EUR=true
// the query parameters take precedence over the cookie
var cookieSecondary string
var cookieUseSecondary bool
cookie, _ := r.Cookie(secondaryCoinCookieName)
if cookie != nil {
a := strings.Split(cookie.Value, "=")
if len(a) == 2 {
cookieSecondary = a[0]
cookieUseSecondary, _ = strconv.ParseBool(a[1])
}
}
secondary := strings.ToLower(r.URL.Query().Get("secondary"))
if secondary == "" {
if cookieSecondary != "" {
secondary = strings.ToLower(cookieSecondary)
} else {
secondary = "usd"
}
}
ticker := s.is.GetCurrentTicker(secondary, "")
if ticker == nil && secondary != "usd" {
secondary = "usd"
ticker = s.is.GetCurrentTicker(secondary, "")
}
if ticker != nil {
t.SecondaryCoin = strings.ToUpper(secondary)
t.CurrentSecondaryCoinRate = float64(ticker.Rates[secondary])
t.UseSecondaryCoin, _ = strconv.ParseBool(r.URL.Query().Get("use_secondary"))
if !t.UseSecondaryCoin {
t.UseSecondaryCoin = cookieUseSecondary
}
}
}
return t
}
func (s *PublicServer) newTemplateDataWithError(text string) *TemplateData {
td := s.newTemplateData()
func (s *PublicServer) newTemplateDataWithError(text string, r *http.Request) *TemplateData {
td := s.newTemplateData(r)
td.Error = &api.APIError{Text: text}
return td
}
@ -359,9 +398,9 @@ func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r
debug.PrintStack()
t = errorInternalTpl
if s.debug {
data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e))
data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e), r)
} else {
data = s.newTemplateDataWithError("Internal server error")
data = s.newTemplateDataWithError("Internal server error", r)
}
}
// noTpl means the handler completely handled the request
@ -387,7 +426,7 @@ func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r
if err != nil || (data == nil && t != noTpl) {
t = errorInternalTpl
if apiErr, ok := err.(*api.APIError); ok {
data = s.newTemplateData()
data = s.newTemplateData(r)
data.Error = apiErr
if apiErr.Public {
t = errorTpl
@ -397,12 +436,16 @@ func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r
glog.Error(handlerName, " error: ", err)
}
if s.debug {
data = s.newTemplateDataWithError(fmt.Sprintf("Internal server error: %v, data %+v", err, data))
data = s.newTemplateDataWithError(fmt.Sprintf("Internal server error: %v, data %+v", err, data), r)
} else {
data = s.newTemplateDataWithError("Internal server error")
data = s.newTemplateDataWithError("Internal server error", r)
}
}
}
// if SecondaryCoin is specified, set secondary_coin cookie
if data != nil && data.SecondaryCoin != "" {
http.SetCookie(w, &http.Cookie{Name: secondaryCoinCookieName, Value: data.SecondaryCoin + "=" + strconv.FormatBool(data.UseSecondaryCoin), Path: "/"})
}
}
}
@ -427,31 +470,37 @@ const (
// TemplateData is used to transfer data to the templates
type TemplateData struct {
CoinName string
CoinShortcut string
CoinLabel string
InternalExplorer bool
ChainType bchain.ChainType
Address *api.Address
AddrStr string
Tx *api.Tx
Error *api.APIError
Blocks *api.Blocks
Block *api.Block
Info *api.SystemInfo
MempoolTxids *api.MempoolTxids
Page int
PrevPage int
NextPage int
PagingRange []int
PageParams template.URL
TOSLink string
SendTxHex string
Status string
NonZeroBalanceTokens bool
TokenId string
URI string
ContractInfo *bchain.ContractInfo
CoinName string
CoinShortcut string
CoinLabel string
InternalExplorer bool
ChainType bchain.ChainType
Address *api.Address
AddrStr string
Tx *api.Tx
Error *api.APIError
Blocks *api.Blocks
Block *api.Block
Info *api.SystemInfo
MempoolTxids *api.MempoolTxids
Page int
PrevPage int
NextPage int
PagingRange []int
PageParams template.URL
TOSLink string
SendTxHex string
Status string
NonZeroBalanceTokens bool
TokenId string
URI string
ContractInfo *bchain.ContractInfo
SecondaryCoin string
UseSecondaryCoin bool
CurrentSecondaryCoinRate float64
TxBlocktime int64
TxDate string
TxBlocktimeSecondaryCoinRate float64
}
func (s *PublicServer) parseTemplates() []*template.Template {
@ -460,6 +509,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
"formatUnixTime": formatUnixTime,
"formatAmount": s.formatAmount,
"formatAmountWithDecimals": formatAmountWithDecimals,
"amount": s.amount,
"formatInt64": formatInt64,
"formatInt": formatInt,
"formatUint32": formatUint32,
@ -605,8 +655,6 @@ func toJSON(data interface{}) string {
return string(json)
}
// for now return the string as it is
// in future could be used to do coin specific formatting
func (s *PublicServer) formatAmount(a *api.Amount) string {
if a == nil {
return "0"
@ -621,6 +669,79 @@ func formatAmountWithDecimals(a *api.Amount, d int) string {
return a.DecimalString(d)
}
func appendAmountSpan(rv *strings.Builder, class, amount, shortcut, txDate string) {
rv.WriteString(`<span class="`)
rv.WriteString(class)
rv.WriteString(`"`)
if txDate != "" {
rv.WriteString(` tm="`)
rv.WriteString(txDate)
rv.WriteString(`"`)
}
rv.WriteString(">")
i := strings.IndexByte(amount, '.')
if i < 0 {
appendSeparatedNumberSpans(rv, amount, "nc")
} else {
appendSeparatedNumberSpans(rv, amount[:i], "nc")
rv.WriteString(`.`)
rv.WriteString(`<span class="amt-dec">`)
appendLeftSeparatedNumberSpans(rv, amount[i+1:], "ns")
rv.WriteString("</span>")
}
rv.WriteString(" ")
rv.WriteString(shortcut)
rv.WriteString("</span>")
}
func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) template.HTML {
primary := s.formatAmount(a)
var rv strings.Builder
rv.WriteString(`<span class="amt`)
if classes != "" {
rv.WriteString(` `)
rv.WriteString(classes)
}
rv.WriteString(`" cc="`)
rv.WriteString(primary)
rv.WriteString(" ")
rv.WriteString(td.CoinShortcut)
rv.WriteString(`">`)
appendAmountSpan(&rv, "prim-amt", primary, td.CoinShortcut, "")
if td.SecondaryCoin != "" {
base, err := strconv.ParseFloat(primary, 64)
if err == nil {
currentSecondary := strconv.FormatFloat(base*td.CurrentSecondaryCoinRate, 'f', 2, 64)
blocktimeSecondary := ""
// if tx is specified, secondary amount is at the time of tx and current amount with class "csec-amt"
if td.Tx != nil {
if td.Tx.Blocktime != td.TxBlocktime {
td.TxBlocktime = td.Tx.Blocktime
date := time.Unix(td.Tx.Blocktime, 0).UTC()
secondary := strings.ToLower(td.SecondaryCoin)
ticker, _ := s.db.FiatRatesFindTicker(&date, secondary, "")
if ticker != nil {
td.TxBlocktimeSecondaryCoinRate = float64(ticker.Rates[secondary])
// the ticker is from the midnight, valid for the whole day before
td.TxDate = date.Add(-1 * time.Second).Format("2006-01-02")
}
}
if td.TxBlocktimeSecondaryCoinRate != 0 {
blocktimeSecondary = strconv.FormatFloat(base*td.TxBlocktimeSecondaryCoinRate, 'f', 2, 64)
}
}
if blocktimeSecondary != "" {
appendAmountSpan(&rv, "sec-amt", blocktimeSecondary, td.SecondaryCoin, td.TxDate)
appendAmountSpan(&rv, "csec-amt", currentSecondary, td.SecondaryCoin, "")
} else {
appendAmountSpan(&rv, "sec-amt", currentSecondary, td.SecondaryCoin, "")
}
}
}
rv.WriteString("</span>")
return template.HTML(rv.String())
}
func formatInt(i int) template.HTML {
return formatInt64(int64(i))
}
@ -629,23 +750,58 @@ func formatUint32(i uint32) template.HTML {
return formatInt64(int64(i))
}
func formatInt64(i int64) template.HTML {
s := strconv.FormatInt(i, 10)
func appendSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
t := (len(s) - 1) / 3
if t <= 0 {
return template.HTML(s)
rv.WriteString(s)
} else {
t *= 3
rv.WriteString(s[:len(s)-t])
for i := len(s) - t; i < len(s); i += 3 {
rv.WriteString(`<span class="`)
rv.WriteString(separatorClass)
rv.WriteString(`">`)
rv.WriteString(s[i : i+3])
rv.WriteString("</span>")
}
}
t *= 3
rv := s[:len(s)-t]
for i := len(s) - t; i < len(s); i += 3 {
rv += `<span class="ns">` + s[i:i+3] + "</span>"
}
func appendLeftSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
l := len(s)
if l <= 3 {
rv.WriteString(s)
} else {
rv.WriteString(s[:3])
for i := 3; i < len(s); i += 3 {
rv.WriteString(`<span class="`)
rv.WriteString(separatorClass)
rv.WriteString(`">`)
e := i + 3
if e > l {
e = l
}
rv.WriteString(s[i:e])
rv.WriteString("</span>")
}
}
return template.HTML(rv)
}
func formatInt64(i int64) template.HTML {
s := strconv.FormatInt(i, 10)
var rv strings.Builder
appendSeparatedNumberSpans(&rv, s, "ns")
return template.HTML(rv.String())
}
// called from template to support txdetail.html functionality
func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
td.Tx = tx
// reset the TxBlocktimeSecondaryCoinRate if different Blocktime
if td.TxBlocktime != tx.Blocktime {
td.TxBlocktime = 0
td.TxBlocktimeSecondaryCoinRate = 0
}
return td
}
@ -704,7 +860,7 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl,
return errorTpl, nil, err
}
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.Tx = tx
return txTpl, data, nil
}
@ -814,7 +970,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
if err != nil {
return errorTpl, nil, err
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.AddrStr = address.AddrStr
data.Address = address
data.Page = address.Page
@ -844,7 +1000,7 @@ func (s *PublicServer) explorerNftDetail(w http.ResponseWriter, r *http.Request)
if ci == nil {
return errorTpl, nil, api.NewAPIError(fmt.Sprintf("Unknown contract %s", contract), true)
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.TokenId = tokenId
data.ContractInfo = ci
data.URI = uri
@ -870,7 +1026,7 @@ func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl
}
return errorTpl, nil, err
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.AddrStr = address.AddrStr
data.Address = address
data.Page = address.Page
@ -895,7 +1051,7 @@ func (s *PublicServer) explorerBlocks(w http.ResponseWriter, r *http.Request) (t
if err != nil {
return errorTpl, nil, err
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.Blocks = blocks
data.Page = blocks.Page
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(blocks.Page, blocks.TotalPages)
@ -916,7 +1072,7 @@ func (s *PublicServer) explorerBlock(w http.ResponseWriter, r *http.Request) (tp
return errorTpl, nil, err
}
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.Block = block
data.Page = block.Page
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(block.Page, block.TotalPages)
@ -931,7 +1087,7 @@ func (s *PublicServer) explorerIndex(w http.ResponseWriter, r *http.Request) (tp
if err != nil {
return errorTpl, nil, err
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.Info = si
return indexTpl, data, nil
}
@ -970,7 +1126,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
func (s *PublicServer) explorerSendTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
s.metrics.ExplorerViews.With(common.Labels{"action": "sendtx"}).Inc()
data := s.newTemplateData()
data := s.newTemplateData(r)
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
@ -1002,7 +1158,7 @@ func (s *PublicServer) explorerMempool(w http.ResponseWriter, r *http.Request) (
if err != nil {
return errorTpl, nil, err
}
data := s.newTemplateData()
data := s.newTemplateData(r)
data.MempoolTxids = mempoolTxids
data.Page = mempoolTxids.Page
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(mempoolTxids.Page, mempoolTxids.TotalPages)

View File

@ -1,86 +1,32 @@
html, body {
@import "TTHoves/TTHoves.css";
* {
margin: 0px;
padding: 0px;
outline: none;
font-family: "TT Hoves", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
html,
body {
height: 100%;
}
body {
width: 100%;
min-height: 100%;
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
font-size: 11px;
margin: 0;
background: linear-gradient(to bottom, #f6f6f6 300px, #e5e5e5 0), #e5e5e5;
background-repeat: no-repeat;
}
a {
color: #428bca;
color: #00854d;
text-decoration: none;
}
h1 small {
font-size: 65%;
}
h3 {
margin-top: 20px;
}
.octicon {
color: #777;
display: inline-block;
vertical-align: text-top;
fill: currentColor;
height: 16px;
}
.navbar-form {
padding-bottom: 1px;
}
.navbar-form .form-control {
background-color: gray;
color: #fff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border: 0;
-webkit-box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
-moz-box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
}
@media (min-width: 768px) {
.container {
max-width: 750px;
}
}
@media (min-width: 992px) {
body {
font-size: 12px;
}
.container {
max-width: 970px;
}
.octicon {
height: 24px;
}
.navbar-form .form-control {
width: 230px;
}
}
@media (min-width: 1200px) {
body {
font-size: 14px;
}
.container {
max-width: 1170px;
}
.octicon {
height: 32px;
}
.navbar-form .form-control {
width: 360px;
}
a:hover {
color: #00854d;
text-decoration: underline;
}
#header {
@ -91,121 +37,136 @@ h3 {
margin: 0;
padding-bottom: 0;
padding-top: 0;
background-color: #212121;
background-color: white;
border: 0;
z-index: 10;
}
.bg-trezor {
background-color: #212121!important;
padding-top: 3px;
padding-bottom: 2px;
z-index: 2;
#header .navbar {
--bs-navbar-padding-y: 0.7rem;
}
.bg-trezor .navbar-brand {
color: rgba(255, 255, 255);
#header .form-control-lg {
font-size: 1rem;
padding: 0.75rem 1rem;
}
#header .container {
min-height: 50px;
}
.bb-group {
border: 0.6rem solid #f6f6f6;
background-color: #f6f6f6;
border-radius: 0.5rem;
position: relative;
display: inline-flex;
vertical-align: middle;
}
.bb-group>.btn {
--bs-btn-padding-x: 0.5rem;
--bs-btn-padding-y: 0.22rem;
--bs-btn-border-radius: 0.3rem;
--bs-btn-border-width: 0;
color: #545454;
}
.bb-group>.btn-check:checked+.btn,
.bb-group .btn.active {
color: black;
font-weight: bold;
font-size: 19px;
fill: #FFFFFF;
background-color: white;
}
@media (max-width: 768px) {
.navbar {
font-size: 14px;
}
.bg-trezor .navbar-brand {
font-weight: normal;
font-size: 14px;
}
.paging {
display: flex;
}
.paging .bb-group>.btn {
min-width: 2rem;
margin-left: 0.1rem;
margin-right: 0.1rem;
}
.paging .bb-group>.btn:hover {
background-color: white;
}
.paging a {
text-decoration: none;
}
.btn-paging {
--bs-btn-color: #757575;
--bs-btn-border-color: #e2e2e2;
--bs-btn-hover-color: black;
--bs-btn-hover-bg: #f6f6f6;
--bs-btn-hover-border-color: #e2e2e2;
--bs-btn-focus-shadow-rgb: 108, 117, 125;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #e2e2e2;
--bs-btn-active-border-color: #e2e2e2;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-gradient: none;
--bs-btn-padding-y: 0.75rem;
--bs-btn-padding-x: 1.1rem;
--bs-btn-border-radius: 0.5rem;
--bs-btn-font-weight: bold;
background-color: #f6f6f6;
}
span.btn-paging {
cursor: initial;
}
span.btn-paging:hover {
color: #757575;
}
.paging-group {
border: 1px solid #e2e2e2;
border-radius: 0.5rem;
}
.paging-group>.bb-group {
border: 0.53rem solid #f6f6f6;
}
#wrap {
min-height: 100%;
height: auto;
padding: 75px 0;
margin: 0 auto -42px;
padding: 112px 0 75px 0;
margin: 0 auto -56px;
}
#footer {
background-color: #212121;
color: #fff;
height: 42px;
background-color: black;
color: #757575;
height: 56px;
overflow: hidden;
}
.alert-data {
color: #383d41;
background-color: #f4f4f4;
border-color: #d6d8db;
padding: 15px;
.navbar-form {
width: 60%;
}
.line-top {
border-top: 1px solid #EAEAEA;
padding: 10px 0 0;
.navbar-form button {
margin-left: -50px;
position: relative;
}
.line-mid {
padding: 15px;
.search-icon {
width: 16px;
height: 16px;
position: absolute;
top: 16px;
background-size: cover;
background-image: url("data:image/svg+xml, %3Csvg style='background: white%3B' width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.24976 12.5C10.1493 12.5 12.4998 10.1495 12.4998 7.25C12.4998 4.35051 10.1493 2 7.24976 2C4.35026 2 1.99976 4.35051 1.99976 7.25C1.99976 10.1495 4.35026 12.5 7.24976 12.5Z' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3Cpath d='M10.962 10.9625L13.9996 14.0001' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3C/svg%3E");
}
.line-bot {
border-bottom: 2px solid #EAEAEA;
padding: 0 0 15px;
}
.txvalues {
display: inline-block;
padding: .7em 2em;
font-size: 13px;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
margin-top: 5px;
}
.txvalues:not(:last-child) {
margin-right: 5px;
}
.txvalues-default {
background-color: #EBEBEB;
color: #333;
}
.txvalues-success {
background-color: dimgray;
}
.txvalues-primary {
background-color: #000;
}
.txvalues-danger {
background-color: #AC0015;
text-transform: uppercase;
}
.tx-own {
background-color: #fbf8f0;
}
.tx-amt {
float: right!important;
}
.tx-in .tx-own .tx-amt {
color: #dc3545!important;
}
.tx-out .tx-own .tx-amt {
color: #28a745!important;
}
.tx-addr {
float: left!important;
.navbar-form ::placeholder {
color: #e2e2e2;
}
.ellipsis {
@ -214,97 +175,359 @@ h3 {
white-space: nowrap;
}
.data-div {
margin: 20px 0 30px 0;
}
.data-div .col-md-10 {
padding-left: 0;
}
.data-table {
table-layout: fixed;
border-radius: .25rem;
background: white;
overflow-wrap: break-word;
margin-left: 8px;
margin-top: 2rem;
margin-bottom: 2rem;
width: calc(100% - 16px);
}
.data-table td, .data-table th {
padding: .4rem;
.data-table thead {
padding-bottom: 20px;
}
.data-table span.ellipsis {
max-width: 100%;
.table.data-table> :not(caption)>*>* {
padding: 0.8rem 0.8rem;
background-color: var(--bs-table-bg);
border-bottom-width: 1px;
box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
}
.data {
font-weight: bold;
.table.data-table>thead>*>* {
padding-bottom: 1.5rem;
}
table.data-table table.data-table th {
border-top: 0;
.table.data-table>*>*:last-child>* {
border-bottom: none;
}
.data-table thead,
.data-table thead tr,
.data-table thead th {
color: #757575;
border: none;
font-weight: normal;
}
.alert .data-table {
margin: 0;
.data-table tbody {
background: white;
border-radius: 8px;
box-shadow: 0 0 0 8px white;
}
::-webkit-input-placeholder {
color: #CCC!important;
font-style: italic;
font-size: 14px;
}
::-moz-placeholder {
color: #CCC!important;
font-style: italic;
font-size: 14px;
}
.h-container ul, .h-container h3 {
margin: 0;
}
.h-container h5 {
margin-top: 6px;
.data-table h3,
.data-table h6 {
margin-bottom: 0;
}
.page-link {
color: #428bca;
.data-table h3 {
color: black;
}
.page-text {
.info-table tbody {
display: inline-table;
width: 100%;
}
.info-table td {
font-weight: bold;
}
.info-table tr>td:first-child {
font-weight: normal;
color: #757575;
}
.ns:before {
content: "";
}
.nc:before {
content: ",";
}
.trezor-logo {
width: 128px;
height: 32px;
position: absolute;
top: 16px;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg style='width: 128px%3B' version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 163.7 41.9' space='preserve'%3E%3Cpolygon points='101.1 12.8 118.2 12.8 118.2 17.3 108.9 29.9 118.2 29.9 118.2 35.2 101.1 35.2 101.1 30.7 110.4 18.1 101.1 18.1'%3E%3C/polygon%3E%3Cpath d='M158.8 26.9c2.1-0.8 4.3-2.9 4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1 7.5h6.7L158.8 26.9z M154.7 22.5h-4V18h4c1.5 0 2.5 0.9 2.5 2.2C157.2 21.6 156.2 22.5 154.7 22.5z'%3E%3C/path%3E%3Cpath d='M130.8 12.5c-6.8 0-11.6 4.9-11.6 11.5s4.9 11.5 11.6 11.5s11.7-4.9 11.7-11.5S137.6 12.5 130.8 12.5z M130.8 30.3c-3.4 0-5.7-2.6-5.7-6.3c0-3.8 2.3-6.3 5.7-6.3c3.4 0 5.8 2.6 5.8 6.3C136.6 27.7 134.2 30.3 130.8 30.3z'%3E%3C/path%3E%3Cpolygon points='82.1 12.8 98.3 12.8 98.3 18 87.9 18 87.9 21.3 98 21.3 98 26.4 87.9 26.4 87.9 30 98.3 30 98.3 35.2 82.1 35.2'%3E%3C/polygon%3E%3Cpath d='M24.6 9.7C24.6 4.4 20 0 14.4 0S4.2 4.4 4.2 9.7v3.1H0v22.3h0l14.4 6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4 9.7c0-2.5 2.2-4.5 5-4.5s5 2 5 4.5v3.1H9.4V9.7z M23 31.5l-8.6 4l-8.6-4V18.1H23V31.5z'%3E%3C/path%3E%3Cpath d='M79.4 20.3c0-4.5-3.1-7.4-7.7-7.4H61.2v22.3H67v-7.5h2.2l4.1 7.5H80l-4.9-8.3C77.2 26.1 79.4 24 79.4 20.3z M71 22.5h-4V18h4c1.5 0 2.5 0.9 2.5 2.2C73.5 21.6 72.5 22.5 71 22.5z'%3E%3C/path%3E%3Cpolygon points='40.5 12.8 58.6 12.8 58.6 18.1 52.4 18.1 52.4 35.2 46.6 35.2 46.6 18.1 40.5 18.1'%3E%3C/polygon%3E%3C/svg%3E");
}
.copyable::before,
.copied::before {
width: 18px;
height: 16px;
margin: 3px -18px;
content: "";
position: absolute;
background-size: cover;
}
.copyable::before {
display: none;
cursor: copy;
background-image: url("data:image/svg+xml,%3Csvg width='18' height='16' viewBox='0 0 18 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 10.4996H13.5V2.49963H5.5V5.49963' stroke='%2300854D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M10.4998 5.49976H2.49976V13.4998H10.4998V5.49976Z' stroke='%2300854D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.copyable:hover::before {
display: inline-block;
}
.copied::before {
transition: all 0.4s ease;
transform: scale(1.2);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='16' viewBox='-30 -30 330 330'%3E%3Cpath d='M 30,180 90,240 240,30' style='stroke:%2300854D; stroke-width:32; fill:none'/%3E%3C/svg%3E");
}
.h-data {
letter-spacing: 0.12em;
font-weight: normal !important;
}
.tx-detail {
background: #f6f6f6;
color: #757575;
border-radius: 10px;
box-shadow: 0 0 0 10px white;
width: calc(100% - 20px);
margin-left: 10px;
margin-top: 3rem;
overflow-wrap: break-word;
}
.tx-detail:first-child {
margin-top: 1rem;
}
.tx-detail:last-child {
margin-bottom: 3rem;
}
.tx-detail span.ellipsis,
.tx-detail a.ellipsis {
display: block;
padding: .5rem .3rem;
line-height: 1.25;
float: left;
max-width: 100%;
}
.page-link {
color: #428bca;
.tx-detail>.head {
padding: 1.5rem;
border-radius: 10px 10px 0 0;
--bs-gutter-x: 0;
}
.page-item.active .page-link {
background-color: #428bca;
.tx-detail .txid {
font-size: 106%;
letter-spacing: -0.01em;
}
#txSpecific {
margin: 0;
.tx-detail>.body {
padding: 0 1.5rem;
--bs-gutter-x: 0;
letter-spacing: -0.01em;
}
.string {
color: darkgreen;
.tx-detail>.footer {
padding: 1.5rem;
--bs-gutter-x: 0;
}
.number, .boolean {
color: darkred;
.tx-in .col-12,
.tx-out .col-12 {
background-color: white;
padding: 1.2rem 1.3rem;
border-bottom: 1px solid #f6f6f6;
}
.null {
.tx-in .col-12:last-child,
.tx-out .co-12l:last-child {
border-bottom: none;
}
.tx-own {
background-color: #fff9e3 !important;
}
.tx-amt {
float: right !important;
}
.spent {
color: #dc3545 !important;
}
.unspent {
color: #28a745 !important;
}
.outpoint {
color: #757575 !important;
}
.spent,
.unspent,
.outpoint {
display: inline-block;
text-align: right;
min-width: 18px;
text-decoration: none !important;
}
.octicon {
height: 24px;
width: 24px;
margin-left: -12px;
margin-top: 19px;
position: absolute;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 4.5L16.5 12L9 19.5' stroke='%23AFAFAF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
}
.txvalue {
color: var(--bs-body-color);
font-weight: bold;
}
.tx-amt .amt:hover,
.tx-amt.amt:hover {
color: var(--bs-body-color);
}
.prim-amt {
display: initial;
}
.sec-amt {
display: none;
}
.csec-amt {
display: none;
}
.tooltip {
--bs-tooltip-opacity: 1;
--bs-tooltip-max-width: 320px;
--bs-tooltip-bg: #fff;
--bs-tooltip-color: var(--bs-body-color);
--bs-tooltip-padding-x: 1rem;
--bs-tooltip-padding-y: 0.8rem;
filter: drop-shadow(0px 24px 64px rgba(22, 27, 45, 0.25));
}
.amt-tooltip {
text-align: start;
display: inline-block;
}
.amt-tooltip .prim-amt,
.amt-tooltip .sec-amt,
.amt-tooltip .csec-amt {
display: initial;
float: right;
}
.amt-dec {
font-size: 97%;
}
.amt-tooltip .amt-time {
padding-right: 3rem;
float: left;
}
.unconfirmed {
color: white;
background-color: #c51e13;
padding: 0.7rem 1.2rem;
border-radius: 1.4rem;
}
.json {
word-wrap: break-word;
font-size: smaller;
background: #002b31;
border-radius: 8px;
}
#raw {
padding: 1.5rem 2rem;
color: #ffffff;
letter-spacing: 0.02em;
}
#raw .string {
color: #2bca87;
}
#raw .number,
#raw .boolean {
color: #efc941;
}
#raw .null {
color: red;
}
.key {
color: #333;
@media (max-width: 768px) {
body {
font-size: 0.8rem;
}
.octicon {
scale: 60% !important;
margin-top: -2px;
}
.btn {
--bs-btn-font-size: 0.8rem;
}
}
@media (max-width: 991px) {
#header .container {
min-height: 40px;
}
.trezor-logo {
top: 10px;
}
.octicon {
scale: 80%;
}
.table.data-table>:not(caption)>*>* {
padding: 0.8rem 0.4rem;
}
}
@media (min-width: 769px) {
body {
font-size: 0.9rem;
}
.btn {
--bs-btn-font-size: 0.9rem;
}
}
@media (min-width: 1200px) {
.h1,
h1 {
font-size: 2.4rem;
}
body {
font-size: 1rem;
}
.btn {
--bs-btn-font-size: 1rem;
}
}

View File

@ -1,483 +0,0 @@
@import "TTHoves/TTHoves.css";
* {
margin: 0px;
padding: 0px;
outline: none;
font-family: "TT Hoves", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
html,
body {
height: 100%;
}
body {
min-height: 100%;
margin: 0;
background: linear-gradient(to bottom, #F6F6F6 300px, #E5E5E5 0), #E5E5E5;
background-repeat: no-repeat;
}
a {
color: #00854D;
text-decoration: none;
}
a:hover {
color: #00854D;
text-decoration: underline;
}
#header {
position: absolute;
top: 0;
left: 0;
width: 100%;
margin: 0;
padding-bottom: 0;
padding-top: 0;
background-color: white;
border: 0;
z-index: 10;
}
#header .navbar {
--bs-navbar-padding-y: 0.7rem;
}
#header .form-control-lg {
font-size: 1rem;
padding: 0.75rem 1rem;
}
#header .container {
min-height: 50px;
}
.bb-group {
border: 0.6rem solid #F6F6F6;
background-color: #F6F6F6;
border-radius: 0.5rem;
position: relative;
display: inline-flex;
vertical-align: middle;
}
.bb-group>.btn {
--bs-btn-padding-x: 0.5rem;
--bs-btn-padding-y: 0.22rem;
--bs-btn-border-radius: 0.3rem;
--bs-btn-border-width: 0;
color: #545454;
}
.bb-group>.btn-check:checked+.btn,
.bb-group .btn.active {
color: black;
font-weight: bold;
background-color: white;
}
.paging {
display: flex;
}
.paging .bb-group>.btn {
min-width: 2rem;
margin-left: 0.1rem;
margin-right: 0.1rem;
}
.paging .bb-group>.btn:hover {
background-color: white;
}
.paging a {
text-decoration: none;
}
.btn-paging {
--bs-btn-color: #757575;
--bs-btn-border-color: #E2E2E2;
--bs-btn-hover-color: black;
--bs-btn-hover-bg: #F6F6F6;
--bs-btn-hover-border-color: #E2E2E2;
--bs-btn-focus-shadow-rgb: 108, 117, 125;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #E2E2E2;
--bs-btn-active-border-color: #E2E2E2;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-gradient: none;
--bs-btn-padding-y: 0.75rem;
--bs-btn-padding-x: 1.1rem;
--bs-btn-border-radius: 0.5rem;
--bs-btn-font-weight: bold;
background-color: #F6F6F6;
}
span.btn-paging {
cursor: initial;
}
span.btn-paging:hover {
color: #757575;
}
.paging-group {
border: 1px solid #E2E2E2;
border-radius: 0.5rem;
}
.paging-group>.bb-group {
border: 0.53rem solid #F6F6F6;
}
#wrap {
min-height: 100%;
height: auto;
padding: 112px 0 75px 0;
margin: 0 auto -56px;
}
#footer {
background-color: black;
color: #757575;
height: 56px;
overflow: hidden;
}
.navbar-form {
width: 60%;
}
.navbar-form button {
margin-left: -50px;
position: relative;
}
.search-icon {
width: 16px;
height: 16px;
position: absolute;
top: 16px;
background-size: cover;
background-image: url("data:image/svg+xml, %3Csvg style='background: white%3B' width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.24976 12.5C10.1493 12.5 12.4998 10.1495 12.4998 7.25C12.4998 4.35051 10.1493 2 7.24976 2C4.35026 2 1.99976 4.35051 1.99976 7.25C1.99976 10.1495 4.35026 12.5 7.24976 12.5Z' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3Cpath d='M10.962 10.9625L13.9996 14.0001' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3C/svg%3E");
}
.navbar-form ::placeholder {
color: #E2E2E2;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.data-table {
table-layout: fixed;
overflow-wrap: break-word;
margin-left: 8px;
margin-top: 2rem;
margin-bottom: 2rem;
width: calc(100% - 16px);
}
.data-table thead {
padding-bottom: 20px;
}
.table.data-table>:not(caption)>*>* {
padding: 0.8rem 0.8rem;
background-color: var(--bs-table-bg);
border-bottom-width: 1px;
box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
}
.table.data-table>thead>*>* {
padding-bottom: 1.5rem;
}
.table.data-table>*>*:last-child>* {
border-bottom: none;
}
.data-table thead,
.data-table thead tr,
.data-table thead th {
color: #757575;
border: none;
font-weight: normal;
}
.data-table tbody {
background: white;
border-radius: 8px;
box-shadow: 0 0 0 8px white;
}
.data-table h3,
.data-table h6 {
margin-bottom: 0;
}
.data-table h3 {
color: black;
}
.info-table tbody {
display: inline-table;
width: 100%;
}
.info-table td {
font-weight: bold;
}
.info-table tr>td:first-child {
font-weight: normal;
color: #757575;
}
.ns:before {
content: "";
}
.trezor-logo {
width: 128px;
height: 32px;
position: absolute;
top: 16px;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg style='width: 128px%3B' version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 163.7 41.9' space='preserve'%3E%3Cpolygon points='101.1 12.8 118.2 12.8 118.2 17.3 108.9 29.9 118.2 29.9 118.2 35.2 101.1 35.2 101.1 30.7 110.4 18.1 101.1 18.1'%3E%3C/polygon%3E%3Cpath d='M158.8 26.9c2.1-0.8 4.3-2.9 4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1 7.5h6.7L158.8 26.9z M154.7 22.5h-4V18h4c1.5 0 2.5 0.9 2.5 2.2C157.2 21.6 156.2 22.5 154.7 22.5z'%3E%3C/path%3E%3Cpath d='M130.8 12.5c-6.8 0-11.6 4.9-11.6 11.5s4.9 11.5 11.6 11.5s11.7-4.9 11.7-11.5S137.6 12.5 130.8 12.5z M130.8 30.3c-3.4 0-5.7-2.6-5.7-6.3c0-3.8 2.3-6.3 5.7-6.3c3.4 0 5.8 2.6 5.8 6.3C136.6 27.7 134.2 30.3 130.8 30.3z'%3E%3C/path%3E%3Cpolygon points='82.1 12.8 98.3 12.8 98.3 18 87.9 18 87.9 21.3 98 21.3 98 26.4 87.9 26.4 87.9 30 98.3 30 98.3 35.2 82.1 35.2'%3E%3C/polygon%3E%3Cpath d='M24.6 9.7C24.6 4.4 20 0 14.4 0S4.2 4.4 4.2 9.7v3.1H0v22.3h0l14.4 6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4 9.7c0-2.5 2.2-4.5 5-4.5s5 2 5 4.5v3.1H9.4V9.7z M23 31.5l-8.6 4l-8.6-4V18.1H23V31.5z'%3E%3C/path%3E%3Cpath d='M79.4 20.3c0-4.5-3.1-7.4-7.7-7.4H61.2v22.3H67v-7.5h2.2l4.1 7.5H80l-4.9-8.3C77.2 26.1 79.4 24 79.4 20.3z M71 22.5h-4V18h4c1.5 0 2.5 0.9 2.5 2.2C73.5 21.6 72.5 22.5 71 22.5z'%3E%3C/path%3E%3Cpolygon points='40.5 12.8 58.6 12.8 58.6 18.1 52.4 18.1 52.4 35.2 46.6 35.2 46.6 18.1 40.5 18.1'%3E%3C/polygon%3E%3C/svg%3E");
}
.copyable::before,
.copied::before {
width: 18px;
height: 16px;
margin: 3px -18px;
content: "";
position: absolute;
background-size: cover;
}
.copyable::before {
display: none;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg width='18' height='16' viewBox='0 0 18 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 10.4996H13.5V2.49963H5.5V5.49963' stroke='%2300854D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M10.4998 5.49976H2.49976V13.4998H10.4998V5.49976Z' stroke='%2300854D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.copyable:hover::before {
display: inline-block;
}
.copied::before {
transition: all 0.4s ease;
transform: scale(1.2);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='16' viewBox='-30 -30 330 330'%3E%3Cpath d='M 30,180 90,240 240,30' style='stroke:%2300854D; stroke-width:32; fill:none'/%3E%3C/svg%3E");
}
.h-data {
letter-spacing: 0.12em;
font-weight: normal !important;
}
.tx-detail {
background: #F6F6F6;
color: #757575;
border-radius: 10px;
box-shadow: 0 0 0 10px white;
width: calc(100% - 20px);
margin-left: 10px;
margin-top: 3rem;
overflow-wrap: break-word;
}
.tx-detail:first-child {
margin-top: 1rem;
}
.tx-detail:last-child {
margin-bottom: 3rem;
}
.tx-detail span.ellipsis,
.tx-detail a.ellipsis {
display: block;
float: left;
max-width: 100%;
}
.tx-detail>.head {
padding: 1.5rem;
border-radius: 10px 10px 0 0;
--bs-gutter-x: 0;
}
.tx-detail .txid {
font-size: 106%;
letter-spacing: -0.01em;
}
.tx-detail>.body {
padding: 0 1.5rem;
--bs-gutter-x: 0;
letter-spacing: -0.01em;
}
.tx-detail>.footer {
padding: 1.5rem;
--bs-gutter-x: 0;
}
.tx-in .col-12,
.tx-out .col-12 {
background-color: white;
padding: 1.2rem 1.3rem;
border-bottom: 1px solid #F6F6F6;
}
.tx-in .col-12:last-child,
.tx-out .co-12l:last-child {
border-bottom: none;
}
.tx-own {
background-color: #FFF9E3 !important;
}
.tx-amt {
float: right !important;
}
.tx-in .tx-own .tx-amt,
.spent {
color: #dc3545 !important;
}
.tx-out .tx-own .tx-amt,
.unspent {
color: #28a745 !important;
}
.outpoint {
color: #757575 !important;
}
.spent,
.unspent,
.outpoint {
display: inline-block;
text-align: right;
min-width: 18px;
text-decoration: none !important;
}
.octicon {
height: 24px;
width: 24px;
margin-left: -12px;
margin-top: 19px;
position: absolute;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 4.5L16.5 12L9 19.5' stroke='%23AFAFAF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
}
.txvalue {
color: black;
font-weight: bold;
}
.unconfirmed {
color: white;
background-color: #C51E13;
padding: 0.7rem 1.2rem;
border-radius: 1.4rem;
}
.json {
word-wrap: break-word;
font-size: smaller;
background: #002B31;
border-radius: 8px;
}
#raw {
padding: 1.5rem 2rem;
color: #FFFFFF;
letter-spacing: 0.02em;
}
#raw .string {
color: #2BCA87;
}
#raw .number,
#raw .boolean {
color: #EFC941;
}
#raw .null {
color: red;
}
@media (max-width: 768px) {
body {
font-size: 0.8rem;
}
.octicon {
scale: 60% !important;
margin-top: -2px;
}
.btn {
--bs-btn-font-size: 0.8rem;
}
}
@media (max-width: 991px) {
#header .container {
min-height: 40px
}
.trezor-logo {
top: 10px;
}
.octicon {
scale: 80%;
}
.table.data-table>:not(caption)>*>* {
padding: 0.8rem 0.4rem;
}
}
@media (min-width: 769px) {
body {
font-size: 0.9rem;
}
.btn {
--bs-btn-font-size: 0.9rem;
}
}
@media (min-width: 1200px) {
.h1,
h1 {
font-size: 2.4rem;
}
body {
font-size: 1rem;
}
.btn {
--bs-btn-font-size: 1rem;
}
}

116
static/js/main.js Normal file
View File

@ -0,0 +1,116 @@
function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 2);
json = json
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
(match) => {
let cls = "number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = "key";
} else {
cls = "string";
}
} else if (/true|false/.test(match)) {
cls = "boolean";
} else if (/null/.test(match)) {
cls = "null";
}
return `<span class="${cls}">${match}</span>`;
}
);
}
function getCoinCookie() {
return document.cookie
.split("; ")
.find((row) => row.startsWith("secondary_coin="))
?.split("=");
}
function changeCSSStyle(selector, cssProp, cssVal) {
const mIndex = 1;
const cssRules = document.all ? "rules" : "cssRules";
for (
i = 0, len = document.styleSheets[mIndex][cssRules].length;
i < len;
i++
) {
if (document.styleSheets[mIndex][cssRules][i].selectorText === selector) {
document.styleSheets[mIndex][cssRules][i].style[cssProp] = cssVal;
return;
}
}
}
function amountTooltip() {
const prim = this.querySelector(".prim-amt");
const sec = this.querySelector(".sec-amt");
const csec = this.querySelector(".csec-amt");
let s = `${prim.outerHTML}<br>`;
if (sec) {
let t = sec.getAttribute("tm");
if (!t) {
t = "now";
}
s += `<span class="amt-time">${t}</span>${sec.outerHTML}<br>`;
}
if (csec) {
s += `<span class="amt-time">now</span>${csec.outerHTML}`;
}
return `<span class="amt-tooltip">${s}</span>`;
}
window.addEventListener("DOMContentLoaded", () => {
const a = getCoinCookie();
if (a?.length === 3) {
if (a[2] === "true") {
changeCSSStyle(".prim-amt", "display", "none");
changeCSSStyle(".sec-amt", "display", "initial");
}
document
.querySelectorAll(".amt")
.forEach(
(e) => new bootstrap.Tooltip(e, { title: amountTooltip, html: true })
);
}
document
.querySelectorAll("[tt]")
.forEach((e) => new bootstrap.Tooltip(e, { title: e.getAttribute("tt") }));
document.querySelectorAll("#header .bb-group>.btn-check").forEach((e) =>
e.addEventListener("click", (e) => {
const a = getCoinCookie();
const sc = e.target.id === "secondary-coin";
if (a?.length === 3 && (a[2] === "true") !== sc) {
document.cookie = `${a[0]}=${a[1]}=${sc}; Path=/`;
changeCSSStyle(".prim-amt", "display", sc ? "none" : "initial");
changeCSSStyle(".sec-amt", "display", sc ? "initial" : "none");
}
})
);
document.querySelectorAll(".copyable").forEach((e) =>
e.addEventListener("click", (e) => {
if (e.clientX < e.target.getBoundingClientRect().x) {
let t = e.target.getAttribute("cc");
if (!t) t = e.target.innerText;
navigator.clipboard.writeText(t);
e.target.className = e.target.className.replace("copyable", "copied");
setTimeout(
() =>
(e.target.className = e.target.className.replace(
"copied",
"copyable"
)),
1000
);
e.preventDefault();
}
})
);
});

View File

@ -5,47 +5,12 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<link rel="stylesheet" href="/static/css/main2.css">
<link rel="stylesheet" href="/static/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script src="/static/js/main.js"></script>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Trezor {{.CoinLabel}} Explorer">
<title>Trezor {{.CoinLabel}} Explorer</title>
<script type="text/javascript">
function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 2);
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
window.onload=()=>{
document.querySelectorAll('[tt]').forEach(e=>new bootstrap.Tooltip(e,{title:e.getAttribute("tt")}));
document.querySelectorAll("#header .bb-group>.btn-check").forEach(e=>e.addEventListener('click',e=>console.log("clicked",e.target.id)));
document.querySelectorAll('.copyable').forEach(e=>e.addEventListener('click',e=>{
console.log("clicked",e.clientX<e.target.getBoundingClientRect().x,e.offsetX, e.clientX, e.target.getBoundingClientRect(), e);
if(e.clientX<e.target.getBoundingClientRect().x){
let t=e.target.getAttribute("copy-content");
if(!t) t=e.target.innerText;
navigator.clipboard.writeText(t);
e.target.className=e.target.className.replace('copyable','copied');
setTimeout(()=>e.target.className=e.target.className.replace('copied','copyable'),1000);
e.preventDefault();
}
}));
}
</script>
</head>
<body>
@ -77,12 +42,14 @@
</button>
</form>
</span>
{{if .SecondaryCoin}}
<div class="bb-group ms-lg-2 mt-2 mt-lg-0" role="group" aria-label="Currency switch">
<input type="radio" class="btn-check" name="btnradio" id="primary-currency" autocomplete="off" checked>
<label class="btn" for="primary-currency">tGOR</label>
<input type="radio" class="btn-check" name="btnradio" id="secondary-currency" autocomplete="off">
<label class="btn" for="secondary-currency">USD</label>
<input type="radio" class="btn-check" name="btnradio" id="primary-coin" autocomplete="off"{{if not .UseSecondaryCoin}} checked{{end}}>
<label class="btn" for="primary-coin">{{.CoinShortcut}}</label>
<input type="radio" class="btn-check" name="btnradio" id="secondary-coin" autocomplete="off"{{if .UseSecondaryCoin}} checked{{end}}>
<label class="btn" for="secondary-coin">{{.SecondaryCoin}}</label>
</div>
{{end}}
</div>
{{end}}
</div>

View File

@ -1,4 +1,4 @@
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
{{define "txdetail"}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
<div class="tx-detail">
<div class="row head">
<div class="col-xs-7 col-md-8">
@ -22,7 +22,7 @@
{{if $vin.Txid}}
<a class="outpoint" href="/tx/{{$vin.Txid}}" tt="Outpoint {{$vin.Txid}},{{$vin.Vout}}"></a>
{{end}}
{{if $vin.Addresses}}<span class="tx-amt copyable">{{formatAmount $vin.ValueSat}} {{$cs}}</span>{{end}}
{{if $vin.Addresses}}{{amount $vin.ValueSat $data "tx-amt copyable"}}{{end}}
</div>
{{else}}
<div class="col-12">No Inputs</div>
@ -42,7 +42,7 @@
<span class="tx-addr">Unparsed address</span>
{{- end -}}
<span class="tx-amt">
<span class="copyable">{{formatAmount $vout.ValueSat}} {{$cs}}</span>{{if $vout.Spent}}<a class="spent" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" tt="Spent"></a>{{else}}<span class="unspent" tt="Unspent">×</span>
{{amount $vout.ValueSat $data "copyable"}}{{if $vout.Spent}}<a class="spent" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" tt="Spent"></a>{{else}}<span class="unspent" tt="Unspent">×</span>
{{end}}
</span>
</td>
@ -56,18 +56,18 @@
<div class="row footer">
<div class="col-sm-12 col-md-4">
{{- if $tx.FeesSat -}}
Fee: <span class="txvalue">{{formatAmount $tx.FeesSat}} {{$cs}}</span>
Fee {{amount $tx.FeesSat $data "txvalue copyable ms-3"}}
{{- end -}}
</div>
<div class="col-sm-12 col-md-8 text-end">
<span class="me-4">
{{- if $tx.Confirmations -}}
<span class="txvalue">{{formatUint32 $tx.Confirmations}}</span> Confirmations
<span class="txvalue">{{formatUint32 $tx.Confirmations}}</span> confirmations
{{- else -}}
<span class="txvalue unconfirmed">Unconfirmed Transaction!</span>
{{- end -}}
</span>
<span class="txvalue">{{formatAmount $tx.ValueOutSat}} {{$cs}}</span>
{{amount $tx.ValueOutSat $data "txvalue copyable"}}
</div>
</div>
</div>