// 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 = `
\u26A0 ${t('spot.unknownAsset')}
`; 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 = `
${headHtml}
-
${rangeCycleButton(range)}
`; 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 = `
${t('spot.noHistory')}
`; 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); }