81 lines
3.6 KiB
JavaScript
81 lines
3.6 KiB
JavaScript
// Generated, do not edit. sha256:f30cb8310951acef1b77d15dedcb9471def9f3c4d10c55de27cbd826be0db541
|
|||
|
|
import { t, localeTag } from '../i18n/index.js';
|
||
|
|
import { withSpot } from './sparkline.js';
|
||
|
|
|
||
|
|
const MS_PER_DAY = 86400000;
|
||
|
|
|
||
|
|
export const RANGE_DAYS = Object.freeze({ week: 7, month: 30, year: 365 });
|
||
|
|
|
||
|
|
export const SPOT_RANGE_DAYS = Object.freeze({ day: 1, ...RANGE_DAYS });
|
||
|
|
|
||
|
|
export function intradayStart(grid, nowMs = Date.now()) {
|
||
|
|
const today = Math.floor(nowMs / MS_PER_DAY) * MS_PER_DAY;
|
||
|
|
const closes = Array.isArray(grid) ? grid.filter(p => p[0] < today) : [];
|
||
|
|
return closes.length ? closes[closes.length - 1][0] + MS_PER_DAY : today;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function rangeSeries(grid, range, spot, nowMs = Date.now(), intraday = null) {
|
||
|
|
if (range === 'day') {
|
||
|
|
const two = withSpot(grid, spot, nowMs).slice(-2);
|
||
|
|
if (two.length < 2 || !Number.isFinite(spot) || !Array.isArray(intraday) || !intraday.length) return two;
|
||
|
|
const start = intradayStart(grid, nowMs);
|
||
|
|
const inner = intraday.filter(([t, p]) => t > start && t < nowMs && Number.isFinite(p));
|
||
|
|
return inner.length ? [[start, two[0][1]], ...inner, [nowMs, spot]] : two;
|
||
|
|
}
|
||
|
|
const cutoff = nowMs - (SPOT_RANGE_DAYS[range] ?? RANGE_DAYS.week) * MS_PER_DAY;
|
||
|
|
const slice = grid.filter(p => p[0] >= cutoff);
|
||
|
|
return withSpot(slice.length >= 2 ? slice : grid, spot, nowMs);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function rangePointLabel(range, priceText, ms, source = null) {
|
||
|
|
const moment = !Number.isFinite(ms) ? null : range === 'day'
|
||
|
|
? new Date(ms).toLocaleString(localeTag(), { ...DATUM, hour: '2-digit', minute: '2-digit' })
|
||
|
|
: utcDay(ms);
|
||
|
|
return [priceText, moment, source].filter(Boolean).join('\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
const DATUM = { day: 'numeric', month: 'short', year: 'numeric' };
|
||
|
|
const utcDay = ms => new Date(ms).toLocaleDateString(localeTag(), { ...DATUM, timeZone: 'UTC' });
|
||
|
|
|
||
|
|
export function namedDaySources(byDay, providers) {
|
||
|
|
const names = new Map((providers || []).map(p => [p.id, p.name]));
|
||
|
|
const out = new Map();
|
||
|
|
for (const [ms, { providerId, sourceName }] of byDay || []) {
|
||
|
|
const name = names.get(providerId);
|
||
|
|
if (name) out.set(ms, sourceName ? t('range.pointVia', { name, source: sourceName }) : name);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function rangePointSources(points, { range, spot = null, nowMs = Date.now(), live = null, intraday = null, intradayAt = null, days = new Map() } = {}) {
|
||
|
|
const out = new Map();
|
||
|
|
if (!Array.isArray(points) || !points.length) return out;
|
||
|
|
const today = Math.floor(nowMs / MS_PER_DAY) * MS_PER_DAY;
|
||
|
|
const own = [...days.keys()].sort((a, b) => a - b);
|
||
|
|
const last = points.length - 1;
|
||
|
|
points.forEach(([ms], i) => {
|
||
|
|
let label = null;
|
||
|
|
if (i === last && Number.isFinite(spot) && (ms === nowMs || ms === today)) label = live;
|
||
|
|
else if (range === 'day' && i > 0 && i < last) label = intradayAt?.get(ms) ?? intraday;
|
||
|
|
else if (days.has(ms)) label = days.get(ms);
|
||
|
|
else {
|
||
|
|
const from = own.filter(d => d < ms).pop();
|
||
|
|
if (from != null && days.get(from)) label = t('range.pointCarried', { bron: days.get(from), dag: utcDay(from) });
|
||
|
|
}
|
||
|
|
if (label) out.set(ms, label);
|
||
|
|
});
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const rangeLabel = r => t('range.' + r);
|
||
|
|
|
||
|
|
export const rangeCycleButton = range =>
|
||
|
|
`<button type="button" class="btn btn-ghost btn-xs range-cycle-btn" title="${t('range.cycle')}" aria-label="${t('range.cycle')}">${rangeLabel(range)}</button>`;
|
||
|
|
|
||
|
|
export function wireRangeCycle(wrap, getRange, onChange, ranges = RANGE_DAYS) {
|
||
|
|
wrap.onclick = e => {
|
||
|
|
if (!e.target.closest('.range-cycle-btn')) return;
|
||
|
|
const order = Object.keys(ranges);
|
||
|
|
onChange(order[(order.indexOf(getRange()) + 1) % order.length]);
|
||
|
|
};
|
||
|
|
}
|