diff --git a/server/public.go b/server/public.go index 5c3f09ac..9edac987 100644 --- a/server/public.go +++ b/server/public.go @@ -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(`") + i := strings.IndexByte(amount, '.') + if i < 0 { + appendSeparatedNumberSpans(rv, amount, "nc") + } else { + appendSeparatedNumberSpans(rv, amount[:i], "nc") + rv.WriteString(`.`) + rv.WriteString(``) + appendLeftSeparatedNumberSpans(rv, amount[i+1:], "ns") + rv.WriteString("") + } + rv.WriteString(" ") + rv.WriteString(shortcut) + rv.WriteString("") +} + +func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) template.HTML { + primary := s.formatAmount(a) + var rv strings.Builder + 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("") + 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(``) + rv.WriteString(s[i : i+3]) + rv.WriteString("") + } } - t *= 3 - rv := s[:len(s)-t] - for i := len(s) - t; i < len(s); i += 3 { - rv += `` + s[i:i+3] + "" +} + +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(``) + e := i + 3 + if e > l { + e = l + } + rv.WriteString(s[i:e]) + rv.WriteString("") + } } - 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) diff --git a/static/css/main.css b/static/css/main.css index 82d4ae64..065d214b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -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; + } } \ No newline at end of file diff --git a/static/css/main2.css b/static/css/main2.css deleted file mode 100644 index 0ea1a4fb..00000000 --- a/static/css/main2.css +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 00000000..77755139 --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,116 @@ +function syntaxHighlight(json) { + json = JSON.stringify(json, undefined, 2); + json = json + .replace(/&/g, "&") + .replace(//g, ">"); + 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 `${match}`; + } + ); +} + +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}
`; + if (sec) { + let t = sec.getAttribute("tm"); + if (!t) { + t = "now"; + } + s += `${t}${sec.outerHTML}
`; + } + if (csec) { + s += `now${csec.outerHTML}`; + } + return `${s}`; +} + +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(); + } + }) + ); +}); diff --git a/static/templates/base.html b/static/templates/base.html index e54a7fb6..4793091d 100644 --- a/static/templates/base.html +++ b/static/templates/base.html @@ -5,47 +5,12 @@ - + + Trezor {{.CoinLabel}} Explorer - @@ -77,12 +42,14 @@ + {{if .SecondaryCoin}}
- - - - + + + +
+ {{end}} {{end}} diff --git a/static/templates/txdetail.html b/static/templates/txdetail.html index 3876b417..49ae9543 100644 --- a/static/templates/txdetail.html +++ b/static/templates/txdetail.html @@ -1,4 +1,4 @@ -{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}} +{{define "txdetail"}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
@@ -22,7 +22,7 @@ {{if $vin.Txid}} {{end}} - {{if $vin.Addresses}}{{formatAmount $vin.ValueSat}} {{$cs}}{{end}} + {{if $vin.Addresses}}{{amount $vin.ValueSat $data "tx-amt copyable"}}{{end}}
{{else}}
No Inputs
@@ -42,7 +42,7 @@ Unparsed address {{- end -}} - {{formatAmount $vout.ValueSat}} {{$cs}}{{if $vout.Spent}}{{else}}× + {{amount $vout.ValueSat $data "copyable"}}{{if $vout.Spent}}{{else}}× {{end}} @@ -56,18 +56,18 @@