add prettier

biome doesn't support formatting html and css in svelte yet, so we use prettier as a workaround for now
This commit is contained in:
wireless_purple 2024-10-12 16:01:45 +00:00
parent 64bcead0b2
commit a921561083
9 changed files with 390 additions and 220 deletions

15
.prettierrc Normal file
View file

@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 80,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View file

@ -1,5 +1,7 @@
# haveno-markets # Haveno Markets
## Requirements ## Requirements
* Haveno
* ./haveno-statsnode --dumpStatistics=true - Haveno
* bun - ./haveno-statsnode --dumpStatistics=true
- bun

BIN
bun.lockb

Binary file not shown.

View file

@ -6,13 +6,15 @@
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"fix": "biome check --write *", "fix": "prettier --write . && biome check --write *",
"lint": "biome check *" "lint": "biome check *"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.3", "@biomejs/biome": "^1.9.3",
"@sveltejs/kit": "^2.6.1", "@sveltejs/kit": "^2.6.1",
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.7", "@sveltejs/vite-plugin-svelte": "^4.0.0-next.7",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5",
"sass": "^1.79.4", "sass": "^1.79.4",
"svelte": "^5.0.0-next.262", "svelte": "^5.0.0-next.262",
"svelte-adapter-bun": "^0.5.2", "svelte-adapter-bun": "^0.5.2",

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Haveno Markets</title> <title>Haveno Markets</title>
<link rel="icon" href="data:;base64,="> <link rel="icon" href="data:;base64,=" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View file

@ -17,12 +17,12 @@ Object.groupBy ||= (values, keyFinder) => {
<div class="col app"> <div class="col app">
<div class="row header" style="align-content:center;"> <div class="row header" style="align-content:center;">
<span> <span>
<img src="/haveno_logo.png" alt="" style="height:1em;"/> <img src="/haveno_logo.png" alt="" style="height:1em;" />
<a href="https://haveno.exchange">haveno.exchange</a> <a href="https://haveno.exchange">haveno.exchange</a>
</span> </span>
<a href="/">haveno.markets</a> <a href="/">haveno.markets</a>
<span> <span>
<img src="/monero_logo.png" alt="" style="height:1em;"/> <img src="/monero_logo.png" alt="" style="height:1em;" />
<a href="https://xmrchain.net">xmrchain.net</a> <a href="https://xmrchain.net">xmrchain.net</a>
</span> </span>
</div> </div>
@ -33,83 +33,99 @@ Object.groupBy ||= (values, keyFinder) => {
<span> <span>
Links: Links:
<a href="https://haveno.markets">Clearnet</a> | <a href="https://haveno.markets">Clearnet</a> |
<a href="http://lsj5o4i7iiogz6q4rstiv75nk7kots2f2nhuga75y7s23leskz2uh6id.onion">Tor</a> | <a
href="http://lsj5o4i7iiogz6q4rstiv75nk7kots2f2nhuga75y7s23leskz2uh6id.onion"
>Tor</a
>
|
<a href="http://haveno-markets.i2p">I2P</a> <a href="http://haveno-markets.i2p">I2P</a>
<a href="http://okoeicsihmjkqcqaiqow3arcrzm5ascwhpxq34incxg6a5z4tjza.b32.i2p">(b32)</a> <a
href="http://okoeicsihmjkqcqaiqow3arcrzm5ascwhpxq34incxg6a5z4tjza.b32.i2p"
>(b32)</a
>
</span> </span>
<span style="display:flex;gap:.2em;"> <span style="display:flex;gap:.2em;">
Data from: Data from:
<a href="https://haveno-reto.com" style="display:inline-flex;gap:.2em;align-items:center;"> <a
<img src="/haveno-reto_logo.svg" alt="" style="height:1em;width:1em;"/> href="https://haveno-reto.com"
style="display:inline-flex;gap:.2em;align-items:center;"
>
<img src="/haveno-reto_logo.svg" alt="" style="height:1em;width:1em;" />
haveno-reto haveno-reto
</a> </a>
</span> </span>
<span> <span>
Donations: Donations:
<a style="word-break:break-all;" href="monero:84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu"> <a
style="word-break:break-all;"
href="monero:84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu"
>
84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu 84LnuW3YpCQirNMN6y6Px1E3DfwnqwXVRARi9eHjzeSVFJqEQmJCxkP5WpkysbcktqUNhXxQLowhJGSknNjJWZNQ7FKp5bu
</a> </a>
</span> </span>
</div> </div>
</div> </div>
<style lang="scss" global> <style lang="scss" global>
.app { .app {
display:flex; display: flex;
width:100%; width: 100%;
justify-content:center; justify-content: center;
min-height: 100dvh; min-height: 100dvh;
} }
.container { .container {
width:80%!important; width: 80% !important;
} }
.header { .header {
background-color:#5555; background-color: #5555;
padding:1em 0; padding: 1em 0;
} }
.footer { .footer {
background-color:#4444; background-color: #4444;
margin-top:auto; margin-top: auto;
text-align:center; text-align: center;
} }
.col { .col {
display:flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width:100%; width: 100%;
height:100%; height: 100%;
} }
.row { .row {
display:flex; display: flex;
flex-direction: row; flex-direction: row;
width:100%; width: 100%;
height:100%; height: 100%;
justify-content: space-around; justify-content: space-around;
} }
body { body {
background-color:#290040; background-color: #290040;
color:white; color: white;
margin:0; margin: 0;
padding:0; padding: 0;
} }
.card { .card {
margin:1em; margin: 1em;
padding:.5em; padding: 0.5em;
background-color:#552A64; background-color: #552a64;
border-radius: 5px; border-radius: 5px;
:global(h4) { :global(h4) {
text-align: center; text-align: center;
color:#F1482D; color: #f1482d;
} }
table { table {
width: 100%; width: 100%;
border-collapse:collapse; border-collapse: collapse;
th, td { th,
text-align:right; td {
padding:.3em; text-align: right;
padding: 0.3em;
} }
th:first-child, td:first-child { th:first-child,
text-align:left; td:first-child {
text-align: left;
} }
tbody tr:nth-child(2n) { tbody tr:nth-child(2n) {
background-color: #0002; background-color: #0002;
@ -119,25 +135,26 @@ Object.groupBy ||= (values, keyFinder) => {
} }
} }
} }
a, .price { a,
.price {
text-decoration: none; text-decoration: none;
color:#f60; color: #f60;
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
.price { .price {
font-size:2em; font-size: 2em;
margin-bottom:.4em; margin-bottom: 0.4em;
} }
h4 { h4 {
margin:.4em; margin: 0.4em;
} }
.trade-currency { .trade-currency {
font-size:1em; font-size: 1em;
color:#fff6; color: #fff6;
font-weight:bold; font-weight: bold;
font-family: monospace; font-family: monospace;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
@ -145,26 +162,26 @@ Object.groupBy ||= (values, keyFinder) => {
flex-direction: column; flex-direction: column;
} }
.container { .container {
width: 97%!important; width: 97% !important;
} }
.card { .card {
margin:.5em 0; margin: 0.5em 0;
padding:.5em 0; padding: 0.5em 0;
} }
.header { .header {
padding:.2em 0; padding: 0.2em 0;
} }
} }
.header > * { .header > * {
display:flex; display: flex;
width:33.3%; width: 33.3%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap:.2em; gap: 0.2em;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.header > * { .header > * {
width:initial; width: initial;
} }
} }
</style> </style>

View file

@ -1,4 +1,5 @@
<svelte:options runes={true} /> <svelte:options runes={true} />
<script> <script>
import { import {
formatPrice, formatPrice,
@ -116,7 +117,9 @@ let w = $state();
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
<h4>XMR/USD</h4> <h4>XMR/USD</h4>
<span class="price">{formatPrice(grouped["USD"][0].price, "USD", true)}</span> <span class="price"
>{formatPrice(grouped["USD"][0].price, "USD", true)}</span
>
</div> </div>
<div class="col card"> <div class="col card">
<h4>Liquidity</h4> <h4>Liquidity</h4>
@ -126,15 +129,27 @@ let w = $state();
<div class="row"> <div class="row">
<div class="col card" style="flex:1;" bind:clientWidth={w}> <div class="col card" style="flex:1;" bind:clientWidth={w}>
<h4>Price XMR/<select bind:value={key}> <h4>
{#each Object.keys(grouped) as key} Price XMR/<select bind:value={key}>
<option>{key}</option> {#each Object.keys(grouped) as key}
{/each} <option>{key}</option>
</select></h4> {/each}
</select>
</h4>
<Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> <Chart
<CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries> width={w - 20}
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> height={300}
container={{ class: "row" }}
layout={chartLayout}
grid={gridLayout}
>
<CandlestickSeries
data={trades}
reactive={true}
priceFormat={{ minMove: 10 ** -precision, precision: precision }}
></CandlestickSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0} />
</Chart> </Chart>
</div> </div>
<div class="col card" style="flex:1"> <div class="col card" style="flex:1">
@ -143,57 +158,94 @@ let w = $state();
<option value="3600000">Hourly</option> <option value="3600000">Hourly</option>
<option value="86400000">Daily</option> <option value="86400000">Daily</option>
<option value="604800000">Weekly</option> <option value="604800000">Weekly</option>
</select> Volume</h4> </select> Volume
<Chart width={w-20} height={300} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> </h4>
<LineSeries data={volume} reactive={true} priceFormat={{precision:2, minMove:.01}}> <Chart
<PriceScale scaleMargins={{bottom:.4, top:.1}}/> width={w - 20}
height={300}
container={{ class: "row" }}
layout={chartLayout}
grid={gridLayout}
>
<LineSeries
data={volume}
reactive={true}
priceFormat={{ precision: 2, minMove: 0.01 }}
>
<PriceScale scaleMargins={{ bottom: 0.4, top: 0.1 }} />
</LineSeries> </LineSeries>
<HistogramSeries data={swaps} reactive={true} priceScaleId="" priceFormat={{precision:0, minMove:1}}> <HistogramSeries
<PriceScale scaleMargins={{top:.7, bottom:0}}/> data={swaps}
reactive={true}
priceScaleId=""
priceFormat={{ precision: 0, minMove: 1 }}
>
<PriceScale scaleMargins={{ top: 0.7, bottom: 0 }} />
</HistogramSeries> </HistogramSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> <TimeScale rightBarStaysOnScroll={true} rightOffset={0} />
</Chart> </Chart>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="card col"> <div class="card col">
<h4>Markets</h4> <h4>Markets</h4>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<th>Currency</th> <th>Currency</th>
<th>Price</th> <th>Price</th>
<th>Trades</th> <th>Trades</th>
</tr> </tr>
{#each Object.values(Object.groupBy(data.trades, ({currency}) => currency)).toSorted((a,b) => b.length - a.length || (b[0].currency < a[0].currency ? 1 : -1)).slice(0, 16) as market} {#each Object.values(Object.groupBy(data.trades, ({ currency }) => currency))
<tr> .toSorted((a, b) => b.length - a.length || (b[0].currency < a[0].currency ? 1 : -1))
<td><a href="market/{market[0].currency}">{getAsset(market[0].currency).name} ({market[0].currency})</a></td> .slice(0, 16) as market}
<td>{formatPrice(market[0].price, market[0].currency, true, false)}</td> <tr>
<td>{market.length}</td> <td
</tr> ><a href="market/{market[0].currency}"
{/each} >{getAsset(market[0].currency).name} ({market[0].currency})</a
</tbody> ></td
</table> >
<h4><a href="markets">View more »</a></h4> <td
>{formatPrice(
market[0].price,
market[0].currency,
true,
false,
)}</td
>
<td>{market.length}</td>
</tr>
{/each}
</tbody>
</table>
<h4><a href="markets">View more »</a></h4>
</div>
<div class="card col">
<h4>Trades</h4>
<table>
<tbody>
<tr>
<th>Date</th>
<th>Amount (XMR)</th>
<th>Amount</th>
</tr>
{#each data.trades.slice(0, 16) as trade}
<tr>
<td
>{new Date(trade.date)
.toISOString()
.replace("T", " ")
.replace(/\.\d*Z/, "")}</td
>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
<td
>{formatPrice(trade.amount, trade.currency, false, false)}
<span class="trade-currency">{trade.currency}</span></td
>
</tr>
{/each}
</tbody>
</table>
<h4><a href="markets">View more »</a></h4>
</div>
</div> </div>
<div class="card col">
<h4>Trades</h4>
<table>
<tbody>
<tr>
<th>Date</th>
<th>Amount (XMR)</th>
<th>Amount</th>
</tr>
{#each data.trades.slice(0, 16) as trade}
<tr>
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
<td>{formatPrice(trade.amount, trade.currency, false, false)} <span class="trade-currency">{trade.currency}</span></td>
</tr>
{/each}
</tbody>
</table>
<h4><a href="markets">View more »</a></h4>
</div>
</div>

View file

@ -1,4 +1,5 @@
<svelte:options runes={true} /> <svelte:options runes={true} />
<script> <script>
import { page } from "$app/stores"; import { page } from "$app/stores";
import { import {
@ -66,60 +67,87 @@ const gridLayout = {
const marketPair = isMoneroQuote(market) ? `${market}/XMR` : `XMR/${market}`; const marketPair = isMoneroQuote(market) ? `${market}/XMR` : `XMR/${market}`;
const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"]; const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"];
</script> </script>
<svelte:head> <svelte:head>
<title>{marketPair} - Haveno Markets</title> <title>{marketPair} - Haveno Markets</title>
</svelte:head> </svelte:head>
<div class="row"> <div class="row">
<div class="col card" bind:clientWidth={w}> <div class="col card" bind:clientWidth={w}>
<h4>{marketPair}</h4> <h4>{marketPair}</h4>
<span class="price">{formatPrice(data.trades?.[0]?.price, market, true, false) || "-"}</span> <span class="price"
>{formatPrice(data.trades?.[0]?.price, market, true, false) || "-"}</span
>
{#if data.trades?.length} {#if data.trades?.length}
<Chart width={w-20} height={500} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> <Chart
<CandlestickSeries data={trades} reactive={true} priceFormat={{minMove:10**-precision, precision:precision}}></CandlestickSeries> width={w - 20}
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> height={500}
container={{ class: "row" }}
layout={chartLayout}
grid={gridLayout}
>
<CandlestickSeries
data={trades}
reactive={true}
priceFormat={{ minMove: 10 ** -precision, precision: precision }}
></CandlestickSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0} />
</Chart> </Chart>
{/if} {/if}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
<h4>Buy Offers</h4> <h4>Buy Offers</h4>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<th>Price</th> <th>Price</th>
<th>Amount (XMR)</th> <th>Amount (XMR)</th>
<th>Amount ({market})</th> <th>Amount ({market})</th>
</tr> </tr>
{#each data.offers[BUY_SELL[0]]?.toSorted((a,b) => b.price - a.price)||[] as offer} {#each data.offers[BUY_SELL[0]]?.toSorted((a, b) => b.price - a.price) || [] as offer}
<tr title={offer.paymentMethod}> <tr title={offer.paymentMethod}>
<td>{formatPrice(offer.price, market, false, false)}</td> <td>{formatPrice(offer.price, market, false, false)}</td>
<td>{formatPrice(offer.amount, "XMR", false, false)}</td> <td>{formatPrice(offer.amount, "XMR", false, false)}</td>
<td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td> <td
</tr> >{formatPrice(
{/each} offer.primaryMarketAmount,
</tbody> market,
</table> false,
</div> false,
<div class="col card"> )}</td
<h4>Sell Offers</h4> >
<table> </tr>
<tbody> {/each}
<tr> </tbody>
<th>Price</th> </table>
<th>Amount (XMR)</th> </div>
<th>Amount ({market})</th> <div class="col card">
</tr> <h4>Sell Offers</h4>
{#each data.offers[BUY_SELL[1]]?.toSorted((a,b) => a.price - b.price)||[] as offer} <table>
<tr title={offer.paymentMethod}> <tbody>
<td>{formatPrice(offer.price, market, false, false)}</td> <tr>
<td>{formatPrice(offer.amount, "XMR", false, false)}</td> <th>Price</th>
<td>{formatPrice(offer.primaryMarketAmount, market, false, false)}</td> <th>Amount (XMR)</th>
</tr> <th>Amount ({market})</th>
{/each} </tr>
</tbody> {#each data.offers[BUY_SELL[1]]?.toSorted((a, b) => a.price - b.price) || [] as offer}
</table> <tr title={offer.paymentMethod}>
</div> <td>{formatPrice(offer.price, market, false, false)}</td>
<td>{formatPrice(offer.amount, "XMR", false, false)}</td>
<td
>{formatPrice(
offer.primaryMarketAmount,
market,
false,
false,
)}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col card"> <div class="col card">
@ -134,7 +162,12 @@ const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"];
</tr> </tr>
{#each data.trades as trade} {#each data.trades as trade}
<tr> <tr>
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td> <td
>{new Date(trade.date)
.toISOString()
.replace("T", " ")
.replace(/\.\d*Z/, "")}</td
>
<td>{formatPrice(trade.price, trade.currency, false, false)}</td> <td>{formatPrice(trade.price, trade.currency, false, false)}</td>
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td> <td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
<td>{formatPrice(trade.amount, trade.currency, false, false)}</td> <td>{formatPrice(trade.amount, trade.currency, false, false)}</td>
@ -143,4 +176,4 @@ const BUY_SELL = isMoneroQuote(market) ? ["SELL", "BUY"] : ["BUY", "SELL"];
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View file

@ -1,4 +1,5 @@
<svelte:options runes={true} /> <svelte:options runes={true} />
<script> <script>
import { crypto, fiat, formatPrice, getAsset } from "$lib/formatPrice"; import { crypto, fiat, formatPrice, getAsset } from "$lib/formatPrice";
import { import {
@ -93,71 +94,119 @@ let w = $state();
<option value="604800000">Weekly</option> <option value="604800000">Weekly</option>
</select> Volume </select> Volume
</h4> </h4>
<Chart width={w-20} height={500} container={{class:"row"}} layout={chartLayout} grid={gridLayout}> <Chart
<LineSeries data={volume} reactive={true} priceFormat={{precision:2, minMove:.01}}> width={w - 20}
<PriceScale scaleMargins={{bottom:.4, top:.1}}/> height={500}
container={{ class: "row" }}
layout={chartLayout}
grid={gridLayout}
>
<LineSeries
data={volume}
reactive={true}
priceFormat={{ precision: 2, minMove: 0.01 }}
>
<PriceScale scaleMargins={{ bottom: 0.4, top: 0.1 }} />
</LineSeries> </LineSeries>
<HistogramSeries data={swaps} reactive={true} priceScaleId="" priceFormat={{precision:0, minMove:1}}> <HistogramSeries
<PriceScale scaleMargins={{top:.7, bottom:0}}/> data={swaps}
reactive={true}
priceScaleId=""
priceFormat={{ precision: 0, minMove: 1 }}
>
<PriceScale scaleMargins={{ top: 0.7, bottom: 0 }} />
</HistogramSeries> </HistogramSeries>
<TimeScale rightBarStaysOnScroll={true} rightOffset={0}/> <TimeScale rightBarStaysOnScroll={true} rightOffset={0} />
</Chart> </Chart>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="card col"> <div class="card col">
<h4>Markets</h4> <h4>Markets</h4>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<th>Currency</th> <th>Currency</th>
<th>Price</th> <th>Price</th>
<th>Offers</th> <th>Offers</th>
<th>Volume (XMR)</th> <th>Volume (XMR)</th>
<th>Trades</th> <th>Trades</th>
</tr> </tr>
{#each Object.values(markets).toSorted((a,b) => (b.trades?.length||0) - (a.trades?.length||0) || (b.offers?.length||0) - (a.offers?.length||0) || (b.code < a.code ? 1 : -1)) as market} {#each Object.values(markets).toSorted((a, b) => (b.trades?.length || 0) - (a.trades?.length || 0) || (b.offers?.length || 0) - (a.offers?.length || 0) || (b.code < a.code ? 1 : -1)) as market}
<tr> <tr>
<td><a href="market/{market.code}">{getAsset(market.code).name} ({market.code})</a></td> <td
<td>{formatPrice(market.trades?.[0]?.price, market.code, true, false) || "-"}</td> ><a href="market/{market.code}"
<td>{market.offers?.length || "-"}</td> >{getAsset(market.code).name} ({market.code})</a
<td>{formatPrice(market.trades?.reduce((a,b) => a + b.xmrAmount, 0), "XMR", false, false) || "-"}</td> ></td
<td>{market.trades?.length || "-"}</td> >
</tr> <td
{/each} >{formatPrice(
</tbody> market.trades?.[0]?.price,
<tfoot> market.code,
<tr> true,
<td></td> false,
<td></td> ) || "-"}</td
<td>{Object.values(data.offers).flat().length}</td> >
<td>{formatPrice(data.trades.reduce((a,b) => a + b.xmrAmount, 0), "XMR", false, false)}</td> <td>{market.offers?.length || "-"}</td>
<td>{data.trades.length}</td> <td
</tr> >{formatPrice(
</tfoot> market.trades?.reduce((a, b) => a + b.xmrAmount, 0),
</table> "XMR",
</div> false,
false,
) || "-"}</td
>
<td>{market.trades?.length || "-"}</td>
</tr>
{/each}
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td>{Object.values(data.offers).flat().length}</td>
<td
>{formatPrice(
data.trades.reduce((a, b) => a + b.xmrAmount, 0),
"XMR",
false,
false,
)}</td
>
<td>{data.trades.length}</td>
</tr>
</tfoot>
</table>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="card col"> <div class="card col">
<h4>Latest Trades</h4> <h4>Latest Trades</h4>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<th>Date</th> <th>Date</th>
<th>Price</th> <th>Price</th>
<th>Amount (XMR)</th> <th>Amount (XMR)</th>
<th>Amount</th> <th>Amount</th>
</tr> </tr>
{#each data.trades.slice(0, 64) as trade} {#each data.trades.slice(0, 64) as trade}
<tr> <tr>
<td>{new Date(trade.date).toISOString().replace("T", " ").replace(/\.\d*Z/, "")}</td> <td
<td>{formatPrice(trade.price, trade.currency, true, false)}</td> >{new Date(trade.date)
<td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td> .toISOString()
<td>{formatPrice(trade.amount, trade.currency, false, false)} <span class="trade-currency">{trade.currency}</span></td> .replace("T", " ")
</tr> .replace(/\.\d*Z/, "")}</td
{/each} >
</tbody> <td>{formatPrice(trade.price, trade.currency, true, false)}</td>
</table> <td>{formatPrice(trade.xmrAmount, "XMR", false, false)}</td>
<td
>{formatPrice(trade.amount, trade.currency, false, false)}
<span class="trade-currency">{trade.currency}</span></td
>
</tr>
{/each}
</tbody>
</table>
</div>
</div> </div>
</div>