Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+169
View File
@@ -0,0 +1,169 @@
// 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);
}