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:
parent
64bcead0b2
commit
a921561083
9 changed files with 390 additions and 220 deletions
15
.prettierrc
Normal file
15
.prettierrc
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 80,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
README.md
10
README.md
|
@ -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
BIN
bun.lockb
Binary file not shown.
|
@ -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",
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
Loading…
Reference in a new issue