169 lines
8.3 KiB
JavaScript
169 lines
8.3 KiB
JavaScript
// Generated, do not edit. sha256:b9ab466fcbb17da69242310739ff9bb1a6f805583cc7c757d134c773ce086dc3
|
|||
|
|
import { t, localeTag } from '../i18n/index.js';
|
||
|
|
import { esc } from './escape.js';
|
||
|
|
import { INFO } from './icons.js';
|
||
|
|
import { openPopover, closePopover } from './popover.js';
|
||
|
|
import { fmtBasePrice, priceFromBase, isBaseAsset as isBase } from './format.js';
|
||
|
|
import { priceUnitLabel } from '../../shared/units.js';
|
||
|
|
import { assetLabel } from './asset-label.js';
|
||
|
|
|
||
|
|
const dagTekst = iso => new Intl.DateTimeFormat(localeTag(), { day: 'numeric', month: 'short', year: 'numeric' })
|
||
|
|
.format(new Date(`${iso}T00:00:00Z`));
|
||
|
|
const tijdTekst = iso => new Intl.DateTimeFormat(localeTag(), { hour: '2-digit', minute: '2-digit' }).format(new Date(iso));
|
||
|
|
const pct = v => `${v > 0 ? '+' : ''}${v.toLocaleString(localeTag(), { maximumFractionDigits: 2 })}%`;
|
||
|
|
|
||
|
|
const STERKTE = { close: 'high', manual: 'high', live: 'high', spot: 'lower', carried: 'low' };
|
||
|
|
|
||
|
|
export function originLines(origin, asset) {
|
||
|
|
if (!origin || origin.fiatPrice == null || isBase(asset)) return [];
|
||
|
|
const prijs = fmtBasePrice(priceFromBase(origin.fiatPrice, asset));
|
||
|
|
const eenheid = asset ? priceUnitLabel(asset) : null;
|
||
|
|
const bron = origin.source || t('priceOrigin.someSource');
|
||
|
|
const regels = [eenheid
|
||
|
|
? t('priceOrigin.pricePer', { prijs, eenheid, dag: dagTekst(origin.day) })
|
||
|
|
: t('priceOrigin.price', { prijs, dag: dagTekst(origin.day) })];
|
||
|
|
if (origin.kind === 'close') regels.push(t('priceOrigin.close', { bron }));
|
||
|
|
else if (origin.kind === 'manual') regels.push(t('priceOrigin.manual'));
|
||
|
|
else if (origin.kind === 'live') regels.push(origin.source ? t('priceOrigin.liveFrom', { bron }) : t('priceOrigin.live'));
|
||
|
|
else if (origin.kind === 'spot') {
|
||
|
|
regels.push(origin.moment ? t('priceOrigin.spotAt', { tijd: tijdTekst(origin.moment), bron }) : t('priceOrigin.spot', { bron }));
|
||
|
|
const d = origin.deviation;
|
||
|
|
const kanten = d ? [d.before != null ? t('priceOrigin.devBefore', { pct: pct(d.before) }) : null,
|
||
|
|
d.after != null ? t('priceOrigin.devAfter', { pct: pct(d.after) }) : null].filter(Boolean) : [];
|
||
|
|
if (kanten.length) regels.push(kanten.join(', '));
|
||
|
|
} else if (origin.kind === 'carried') {
|
||
|
|
regels.push(t('priceOrigin.carried', { dag: dagTekst(origin.sourceDay || origin.day), n: origin.ageDays, bron }));
|
||
|
|
}
|
||
|
|
const sterkte = STERKTE[origin.kind];
|
||
|
|
if (sterkte) regels.push(t('priceOrigin.strength', { sterkte: t(`priceOrigin.strength_${sterkte}`) }));
|
||
|
|
return regels;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function originHtml(origin, asset, voor = []) {
|
||
|
|
return knopHtml([...voor, ...originLines(origin, asset)]);
|
||
|
|
}
|
||
|
|
|
||
|
|
const RANG = { high: 3, lower: 2, low: 1 };
|
||
|
|
const rang = o => RANG[STERKTE[o.kind]] ?? 0;
|
||
|
|
|
||
|
|
export function originSummaryLines(items) {
|
||
|
|
return samenvattingVan(items).regels;
|
||
|
|
}
|
||
|
|
|
||
|
|
function samenvattingVan(items) {
|
||
|
|
const assetSleutel = x => (x.asset ? x.asset.id ?? x.asset.symbol : '?');
|
||
|
|
const uniek = new Map();
|
||
|
|
const zonder = new Map();
|
||
|
|
for (const x of items || []) {
|
||
|
|
if (!x || isBase(x.asset)) continue;
|
||
|
|
if (!x.origin || x.origin.fiatPrice == null) { if (!zonder.has(assetSleutel(x))) zonder.set(assetSleutel(x), x); continue; }
|
||
|
|
const sleutel = `${assetSleutel(x)}|${x.origin.day}`;
|
||
|
|
if (!uniek.has(sleutel)) uniek.set(sleutel, x);
|
||
|
|
}
|
||
|
|
const met = [...uniek.values()];
|
||
|
|
if (!met.length && !zonder.size) return { regels: [], onvolledig: false };
|
||
|
|
if (zonder.size) {
|
||
|
|
const eigen = met.length === 1
|
||
|
|
? originLines(met[0].origin, met[0].asset).map((r, i) => (i === 0 ? `${assetLabel(met[0].asset)}: ${r}` : r))
|
||
|
|
: met.length ? samenvatting(met) : [];
|
||
|
|
const zonderSterkte = eigen.length ? eigen.slice(0, -1) : [];
|
||
|
|
const namen = [...zonder.values()].map(x => assetLabel(x.asset)).join(', ');
|
||
|
|
return { onvolledig: true, regels: [...zonderSterkte, t('priceOrigin.sumMissing', { assets: namen }),
|
||
|
|
t('priceOrigin.strength', { sterkte: t('priceOrigin.strength_missing') })] };
|
||
|
|
}
|
||
|
|
return { onvolledig: false, regels: met.length === 1 ? originLines(met[0].origin, met[0].asset) : samenvatting(met) };
|
||
|
|
}
|
||
|
|
|
||
|
|
function samenvatting(met) {
|
||
|
|
const telling = new Map();
|
||
|
|
for (const { origin } of met) telling.set(origin.kind, (telling.get(origin.kind) || 0) + 1);
|
||
|
|
const soorten = ['close', 'manual', 'live', 'spot', 'carried'].filter(k => telling.has(k))
|
||
|
|
.map(k => { const n = telling.get(k); return `${n} ${t(`priceHistory.kindCount_${k}${n === 1 ? 'One' : 'Many'}`)}`; });
|
||
|
|
const zwakste = met.reduce((a, b) => (rang(b.origin) < rang(a.origin)
|
||
|
|
|| (rang(b.origin) === rang(a.origin) && (b.origin.ageDays || 0) > (a.origin.ageDays || 0)) ? b : a));
|
||
|
|
const regels = [t('priceOrigin.sumHead', { n: met.length }), soorten.join(', ')];
|
||
|
|
if (STERKTE[zwakste.origin.kind] !== 'high') {
|
||
|
|
const zin = originLines(zwakste.origin, zwakste.asset)[1] || '';
|
||
|
|
regels.push(t('priceOrigin.sumWeakest', { asset: assetLabel(zwakste.asset), zin: zin.charAt(0).toLowerCase() + zin.slice(1) }));
|
||
|
|
}
|
||
|
|
regels.push(t('priceOrigin.strength', { sterkte: t(`priceOrigin.strength_${STERKTE[zwakste.origin.kind]}`) }));
|
||
|
|
return regels;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function originsHtml(items, voor = []) {
|
||
|
|
const { regels, onvolledig } = samenvattingVan(items);
|
||
|
|
return knopHtml([...voor, ...regels], { warn: onvolledig });
|
||
|
|
}
|
||
|
|
|
||
|
|
export function warningHtml(regels) {
|
||
|
|
return knopHtml(regels, { warn: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
const BOEKING_STERKTE = { paid: 'high', market_moment: 'high', market_day: 'lower' };
|
||
|
|
|
||
|
|
export function bookingOriginLines(b, asset, dayOrigin = null) {
|
||
|
|
if (!b || b.priceOrigin === 'none' || b.fiatPrice == null || isBase(asset)) return [];
|
||
|
|
const prijs = fmtBasePrice(priceFromBase(b.fiatPrice, asset));
|
||
|
|
const eenheid = asset ? priceUnitLabel(asset) : null;
|
||
|
|
const dag = dagTekst(String(b.occurredAt).slice(0, 10));
|
||
|
|
const regels = [eenheid ? t('priceOrigin.bookingPricePer', { prijs, eenheid }) : t('priceOrigin.bookingPrice', { prijs })];
|
||
|
|
if (b.priceOrigin === 'paid') regels.push(t('priceOrigin.bookingPaid'));
|
||
|
|
else if (b.priceOrigin === 'market_moment') regels.push(t('priceOrigin.bookingMoment'));
|
||
|
|
else if (b.priceOrigin === 'market_day') {
|
||
|
|
const zin = dayOrigin ? originLines(dayOrigin, asset)[1] : null;
|
||
|
|
regels.push(zin ? t('priceOrigin.bookingDayFrom', { dag, zin: zin.charAt(0).toLowerCase() + zin.slice(1) })
|
||
|
|
: t('priceOrigin.bookingDay', { dag }));
|
||
|
|
regels.push(t('priceOrigin.bookingDayNote'));
|
||
|
|
} else {
|
||
|
|
regels.push(t('priceOrigin.bookingUnknown'));
|
||
|
|
}
|
||
|
|
const sterkte = b.priceOrigin === 'market_day' && dayOrigin && dayOrigin.kind === 'carried'
|
||
|
|
? 'low' : (BOEKING_STERKTE[b.priceOrigin] || 'unknown');
|
||
|
|
regels.push(t('priceOrigin.strength', { sterkte: t(`priceOrigin.strength_${sterkte}`) }));
|
||
|
|
return regels;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function bookingOriginHtml(b, asset, dayOrigin = null) {
|
||
|
|
return knopHtml(bookingOriginLines(b, asset, dayOrigin));
|
||
|
|
}
|
||
|
|
|
||
|
|
export function infoLinesHtml(regels) {
|
||
|
|
return knopHtml(regels);
|
||
|
|
}
|
||
|
|
|
||
|
|
function knopHtml(regels, { warn = false } = {}) {
|
||
|
|
if (!regels.length) return '';
|
||
|
|
return `<button type="button" class="po-btn${warn ? ' is-warn' : ''}" title="" data-origin-lines="${esc(JSON.stringify(regels))}"`
|
||
|
|
+ ` aria-label="${esc(regels.join('. '))}">${INFO}</button>`;
|
||
|
|
}
|
||
|
|
|
||
|
|
let openFor = null;
|
||
|
|
function toon(btn) {
|
||
|
|
if (openFor === btn) return;
|
||
|
|
let regels = [];
|
||
|
|
try { regels = JSON.parse(btn.dataset.originLines || '[]'); } catch { regels = []; }
|
||
|
|
if (!regels.length) return;
|
||
|
|
const pop = openPopover(btn, { className: 'po-pop', onClose: () => { openFor = null; } });
|
||
|
|
pop.el.setAttribute('role', 'tooltip');
|
||
|
|
pop.el.innerHTML = regels.map((r, i) => `<div class="${i === 0 ? 'po-head' : 'po-line'}">${esc(r)}</div>`).join('');
|
||
|
|
pop.position();
|
||
|
|
openFor = btn;
|
||
|
|
}
|
||
|
|
function verberg(btn) { if (openFor === btn) closePopover(); }
|
||
|
|
|
||
|
|
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
||
|
|
const btnVan = e => (e.target && e.target.closest ? e.target.closest('.po-btn') : null);
|
||
|
|
document.addEventListener('mouseover', e => { const b = btnVan(e); if (b) toon(b); });
|
||
|
|
document.addEventListener('mouseout', e => {
|
||
|
|
const b = btnVan(e);
|
||
|
|
if (b && !(e.relatedTarget && b.contains(e.relatedTarget))) verberg(b);
|
||
|
|
});
|
||
|
|
document.addEventListener('focusin', e => { const b = btnVan(e); if (b) toon(b); });
|
||
|
|
document.addEventListener('focusout', e => { const b = btnVan(e); if (b) verberg(b); });
|
||
|
|
document.addEventListener('click', e => {
|
||
|
|
const b = btnVan(e);
|
||
|
|
if (!b) return;
|
||
|
|
e.stopPropagation();
|
||
|
|
toon(b);
|
||
|
|
}, true);
|
||
|
|
}
|