Files
AtreidesAssetsFree/client/ui/range-cycle.js
T

81 lines
3.6 KiB
JavaScript
Raw Normal View History

2026-10-29 11:57:29 +01:00
// 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]);
};
}