Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,172 @@
|
||||
// Generated, do not edit. sha256:a0b282c1ed4d4e3e20c57f97c21ad932b410360a0adabc7ef61e694ce97a0ed9
|
||||
import { listAssets } from '../data/assets.js';
|
||||
import { getPref, setPref } from '../data/preferences.js';
|
||||
import { baseCurrency } from '../app-settings.js';
|
||||
import { listPriceHistory } from '../data/price-history.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { toDailyGrid, dailyGridSources } from '../signals/data.js';
|
||||
import { liveSpotFor, basePriceOf, lastSpotSource } from '../price/providers.js';
|
||||
import { listProviders } from '../data/providers.js';
|
||||
import { fmtMoney, colouredSigns } from '../ui/format.js';
|
||||
import { assetLabelHtml } from '../ui/asset-label.js';
|
||||
import { priceFromBase } from '../../shared/units.js';
|
||||
import { secondCurrencyOf, secondAmount, renderSecondCurrencySetting } from './second-currency.js';
|
||||
import { mountSparkline, changePctHtml, sliceTone } from '../ui/sparkline.js';
|
||||
import { RANGE_DAYS, SPOT_RANGE_DAYS, rangeSeries, intradayStart, rangeLabel, rangeCycleButton, wireRangeCycle, rangePointLabel, rangePointSources, namedDaySources } from '../ui/range-cycle.js';
|
||||
import { loadIntraday } from '../price/intraday.js';
|
||||
import { momentumOn, momentumDotHtml, renderMomentumSetting } from './momentum-dot.js';
|
||||
import { loadMomentum } from '../price/momentum.js';
|
||||
|
||||
const MS_PER_DAY = 86400000;
|
||||
|
||||
function fromDateStr(days) {
|
||||
return new Date(Date.now() - (days + 5) * MS_PER_DAY).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
const liveHosts = new Set();
|
||||
let spotBound = false;
|
||||
|
||||
async function refreshOneSpot(entry) {
|
||||
try {
|
||||
const spot = await liveSpotFor(entry.asset);
|
||||
if (!entry.host.isConnected) { liveHosts.delete(entry); return; }
|
||||
const live = basePriceOf(spot);
|
||||
if (live == null) return;
|
||||
entry.setLive(live);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
async function refreshAllSpots() {
|
||||
for (const entry of liveHosts) {
|
||||
if (!entry.host.isConnected) { liveHosts.delete(entry); continue; }
|
||||
refreshOneSpot(entry);
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderSpotSettings(host, instance, commit) {
|
||||
await renderSecondCurrencySetting(host, instance, commit);
|
||||
renderMomentumSetting(host, instance, commit);
|
||||
}
|
||||
|
||||
export async function renderSpotWidget(host, instance) {
|
||||
const assetId = instance && instance.params ? instance.params.assetId : null;
|
||||
const asset = (await listAssets()).find(a => a.id === assetId);
|
||||
if (!asset) {
|
||||
host.innerHTML = `<div class="card"><div class="t-body-sm t-muted">\u26A0 ${t('spot.unknownAsset')}</div></div>`;
|
||||
return;
|
||||
}
|
||||
const cur = baseCurrency();
|
||||
const headHtml = assetLabelHtml(asset);
|
||||
const inUnit = p => (p == null ? null : priceFromBase(p, asset));
|
||||
const second = secondCurrencyOf(instance);
|
||||
const fmtOpts = p => ({ decimals: p >= 1000 ? 0 : 2 });
|
||||
|
||||
let range = await getPref('widget.spot', String(assetId), 'week');
|
||||
if (!SPOT_RANGE_DAYS[range]) range = 'week';
|
||||
|
||||
host.innerHTML = `<div class="card spot-card">
|
||||
<div class="flex-between spot-head-row">
|
||||
<div class="t-label spot-head">${headHtml}</div>
|
||||
<div class="spot-price"><span id="spot-price">-</span><span class="amount-second hidden" id="spot-second"></span></div>
|
||||
</div>
|
||||
<div class="flex-between spot-sub">
|
||||
<div class="spot-change" id="spot-change"></div>
|
||||
<div class="range-cycle" id="spot-range">${rangeCycleButton(range)}</div>
|
||||
</div>
|
||||
<div class="spot-spark-wrap" id="spot-spark"></div>
|
||||
<div class="t-body-sm t-neg hidden" id="spot-err"></div>
|
||||
</div>`;
|
||||
|
||||
let grid = null;
|
||||
let live = null;
|
||||
let intraday = null;
|
||||
let intradaySource = null;
|
||||
let intradaySources = null;
|
||||
let daySources = new Map();
|
||||
const priceEl = host.querySelector('#spot-price');
|
||||
const changeEl = host.querySelector('#spot-change');
|
||||
const sparkEl = host.querySelector('#spot-spark');
|
||||
const secondEl = host.querySelector('#spot-second');
|
||||
|
||||
async function showSecond(price) {
|
||||
if (!second) return;
|
||||
const tekst = await secondAmount(price, second, fmtOpts(price));
|
||||
secondEl.textContent = tekst || '';
|
||||
secondEl.classList.toggle('hidden', !tekst);
|
||||
}
|
||||
|
||||
function setLive(basePrice) {
|
||||
const price = inUnit(basePrice);
|
||||
if (!Number.isFinite(price)) return;
|
||||
live = price;
|
||||
priceEl.textContent = fmtMoney(price, cur, fmtOpts(price));
|
||||
showSecond(price);
|
||||
draw();
|
||||
refreshIntraday();
|
||||
}
|
||||
|
||||
async function refreshIntraday() {
|
||||
if (range !== 'day' || !grid) return;
|
||||
const r = await loadIntraday(asset.id, intradayStart(grid));
|
||||
if (!host.isConnected) return;
|
||||
intraday = r.points ? r.points.map(([ms, p]) => [ms, inUnit(p)]) : null;
|
||||
intradaySource = r.source || null;
|
||||
intradaySources = r.sources || null;
|
||||
draw();
|
||||
}
|
||||
|
||||
const entry = { host, asset, setLive };
|
||||
liveHosts.add(entry);
|
||||
if (!spotBound) { document.addEventListener('tick:spot', refreshAllSpots); spotBound = true; }
|
||||
|
||||
function draw() {
|
||||
const rb = host.querySelector('.range-cycle-btn');
|
||||
if (rb) rb.textContent = rangeLabel(range);
|
||||
if (!grid) return;
|
||||
const nowMs = Date.now();
|
||||
const use = rangeSeries(grid, range, live, nowMs, range === 'day' ? intraday : null);
|
||||
const sources = rangePointSources(use, { range, spot: live, nowMs, live: lastSpotSource(asset),
|
||||
intraday: intradaySource, intradayAt: intradaySources, days: daySources });
|
||||
mountSparkline(sparkEl, use, {
|
||||
tone: sliceTone(use),
|
||||
formatValue: (p, ms) => rangePointLabel(range, fmtMoney(p, cur, { decimals: p >= 1000 ? 0 : 2 }), ms, sources.get(ms)),
|
||||
});
|
||||
changeEl.innerHTML = changePctHtml(use, momentumDotHtml(live != null ? asset.id : null, momentumOn(instance)), { fine: range === 'day', signs: colouredSigns() });
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
try {
|
||||
const [spot, rows, , providers] = await Promise.all([
|
||||
liveSpotFor(asset).catch(() => null),
|
||||
Promise.resolve(await listPriceHistory(assetId, fromDateStr(RANGE_DAYS.year))),
|
||||
loadMomentum(asset.id),
|
||||
listProviders().catch(() => []),
|
||||
]);
|
||||
|
||||
try { grid = toDailyGrid(rows).map(([d, p]) => [d, inUnit(p)]); }
|
||||
catch { grid = null; }
|
||||
daySources = namedDaySources(dailyGridSources(rows), providers);
|
||||
|
||||
const spotNu = inUnit(basePriceOf(spot));
|
||||
const lastClose = grid && grid.length ? grid[grid.length - 1][1] : null;
|
||||
if (spotNu != null) live = spotNu;
|
||||
const price = spotNu != null ? spotNu : lastClose;
|
||||
priceEl.textContent = price != null ? fmtMoney(price, cur, fmtOpts(price)) : '-';
|
||||
if (price != null) showSecond(price);
|
||||
|
||||
if (!grid) sparkEl.innerHTML = `<div class="spot-spark-empty">${t('spot.noHistory')}</div>`;
|
||||
else { draw(); refreshIntraday(); }
|
||||
} catch (err) {
|
||||
console.error('[spot-widget]', err);
|
||||
const e = host.querySelector('#spot-err');
|
||||
if (e) { e.classList.remove('hidden'); e.textContent = '\u26A0 ' + err.message; }
|
||||
}
|
||||
|
||||
wireRangeCycle(host.querySelector('#spot-range'), () => range, next => {
|
||||
range = next;
|
||||
setPref('widget.spot', String(assetId), range).catch(() => { });
|
||||
draw();
|
||||
refreshIntraday();
|
||||
}, SPOT_RANGE_DAYS);
|
||||
}
|
||||
Reference in new issue
Block a user