Files
brk/website/src/heatmap/controls/dates.js
T
2026-06-01 13:20:34 +02:00

171 lines
4.4 KiB
JavaScript

import { createSelect } from "../../../scripts/utils/dom.js";
import { GENESIS_DATE, todayISODate, toISODate } from "../time.js";
import { createHeatmapPersistedValue, findChoiceByKey } from "./shared.js";
/**
* @typedef {Object} RangeChoice
* @property {string} label
* @property {string} date
*/
/**
* @param {HeatmapOption} option
* @param {(range: { from: string, to: string }) => void} onChange
*/
export function createDateControls(option, onChange) {
const currentYear = new Date().getUTCFullYear();
const fromChoices = createFromChoices(currentYear);
const toChoices = createToChoices(currentYear);
const fallbackFromChoice = fromChoices.at(-1) ?? fromChoices[0];
const fallbackToChoice = toChoices[0];
const defaultFromChoice = findChoiceByKey(
fromChoices,
option.defaults?.from ?? "",
fallbackFromChoice,
rangeChoiceLabel,
);
const defaultToChoice = findChoiceByKey(
toChoices,
option.defaults?.to ?? "",
fallbackToChoice,
rangeChoiceLabel,
);
const persistedFrom = createHeatmapPersistedValue(
option,
"from",
"hm_from",
rangeChoiceLabel(defaultFromChoice),
);
const persistedTo = createHeatmapPersistedValue(
option,
"to",
"hm_to",
rangeChoiceLabel(defaultToChoice),
);
let fromChoice = findChoiceByKey(
fromChoices,
persistedFrom.value,
defaultFromChoice,
rangeChoiceLabel,
);
let toChoice = findChoiceByKey(
toChoices,
persistedTo.value,
defaultToChoice,
rangeChoiceLabel,
);
if (fromChoice.date > toChoice.date) {
toChoice = findSameLabelChoice(toChoices, fromChoice, defaultToChoice);
}
persistDateChoices();
const fromSelect = createSelect({
id: "heatmap-from",
label: "from",
choices: fromChoices,
initialValue: fromChoice,
onChange(choice) {
fromChoice = choice;
if (fromChoice.date > toChoice.date) {
toChoice = findSameLabelChoice(toChoices, fromChoice, defaultToChoice);
toSelect.set(toChoice);
}
persistDateChoices();
onChange({ from: fromChoice.date, to: toChoice.date });
},
toKey: rangeChoiceLabel,
toLabel: rangeChoiceLabel,
});
const toSelect = createSelect({
id: "heatmap-to",
label: "to",
choices: toChoices,
initialValue: toChoice,
onChange(choice) {
toChoice = choice;
if (fromChoice.date > toChoice.date) {
fromChoice = findSameLabelChoice(
fromChoices,
toChoice,
defaultFromChoice,
);
fromSelect.set(fromChoice);
}
persistDateChoices();
onChange({ from: fromChoice.date, to: toChoice.date });
},
toKey: rangeChoiceLabel,
toLabel: rangeChoiceLabel,
});
return {
elements: [fromSelect.element, toSelect.element],
from: fromChoice.date,
to: toChoice.date,
};
function persistDateChoices() {
persistedFrom.setImmediate(rangeChoiceLabel(fromChoice));
persistedTo.setImmediate(rangeChoiceLabel(toChoice));
}
}
/**
* @param {number} currentYear
* @returns {RangeChoice[]}
*/
function createFromChoices(currentYear) {
const choices = [{ label: "genesis", date: GENESIS_DATE }];
for (let year = 2009; year <= currentYear; year++) {
choices.push({
label: String(year),
date: year === 2009 ? GENESIS_DATE : yearStartISODate(year),
});
}
return choices;
}
/**
* @param {number} currentYear
* @returns {RangeChoice[]}
*/
function createToChoices(currentYear) {
const today = todayISODate();
const todayTime = Date.parse(`${today}T00:00:00Z`);
const choices = [{ label: "today", date: today }];
for (let year = currentYear; year >= 2009; year--) {
choices.push({ label: String(year), date: yearEndISODate(year, todayTime) });
}
return choices;
}
/** @param {RangeChoice} choice */
function rangeChoiceLabel(choice) {
return choice.label;
}
/**
* @param {readonly RangeChoice[]} choices
* @param {RangeChoice} choice
* @param {RangeChoice} fallback
*/
function findSameLabelChoice(choices, choice, fallback) {
return choices.find((candidate) => candidate.label === choice.label) ?? fallback;
}
/** @param {number} year */
function yearStartISODate(year) {
return toISODate(new Date(Date.UTC(year, 0, 1)));
}
/**
* @param {number} year
* @param {number} todayTime
*/
function yearEndISODate(year, todayTime) {
return toISODate(new Date(Math.min(Date.UTC(year, 11, 31), todayTime)));
}