2026-11-06 08:28:04 +00:00
|
|
|
|
function windowQuery() {
|
|
|
|
|
|
if (ui.preset === -1) {
|
|
|
|
|
|
return `from=${Math.floor(ui.from)}&to=${Math.ceil(ui.to)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
const end = ui.asOf === null ? nowSeconds() : ui.asOf;
|
|
|
|
|
|
const begin = ui.preset === 0 ? 0 : end - ui.preset;
|
|
|
|
|
|
return `from=${Math.floor(begin)}${ui.asOf === null ? "" : `&to=${Math.ceil(end)}`}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const localInput = (ts) => {
|
|
|
|
|
|
const date = stampOf(ts);
|
|
|
|
|
|
return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-11-06 22:20:06 +00:00
|
|
|
|
function customRange() {
|
|
|
|
|
|
const from = h("input", {type: "datetime-local", "aria-label": "Von", value: localInput(ui.from === null ? state.since : ui.from)});
|
|
|
|
|
|
const to = h("input", {type: "datetime-local", "aria-label": "Bis", value: localInput(ui.to === null ? state.until : ui.to)});
|
|
|
|
|
|
return h("span", {class: "custom"}, from, h("span", {class: "muted"}, "bis"), to,
|
2026-11-06 08:28:04 +00:00
|
|
|
|
h("button", {type: "button", class: "btn small soft", on: {click: () => {
|
|
|
|
|
|
const begin = Date.parse(from.value) / 1000;
|
|
|
|
|
|
const end = Date.parse(to.value) / 1000;
|
|
|
|
|
|
if (!Number.isFinite(begin) || !Number.isFinite(end) || begin >= end) {
|
|
|
|
|
|
toast("Zeitraum ungültig", ["„Von“ muss vor „Bis“ liegen."], false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-11-06 22:20:06 +00:00
|
|
|
|
stopReplay();
|
|
|
|
|
|
ui.preset = -1;
|
2026-11-06 08:28:04 +00:00
|
|
|
|
ui.from = begin;
|
|
|
|
|
|
ui.to = end;
|
|
|
|
|
|
reload();
|
|
|
|
|
|
}}}, "Anwenden"));
|
2026-11-06 22:20:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function timeBar() {
|
|
|
|
|
|
const bar = document.getElementById("time");
|
|
|
|
|
|
const presets = h("div", {class: "segmented", role: "group", "aria-label": "Zeitraum"});
|
|
|
|
|
|
const summary = h("summary", {title: "Zeitraum und Wiedergabe"});
|
2026-11-06 08:28:04 +00:00
|
|
|
|
const slider = h("input", {type: "range", min: 0, max: 1000, step: 1, value: 1000, "aria-label": "Stand der Wiedergabe"});
|
|
|
|
|
|
const play = h("button", {type: "button", class: "btn small soft"}, "▶ Wiedergabe");
|
|
|
|
|
|
const liveButton = h("button", {type: "button", class: "btn small ghost"}, "Live");
|
|
|
|
|
|
const label = h("span", {class: "window-label"});
|
2026-11-06 22:20:06 +00:00
|
|
|
|
for (const [seconds, name] of PRESETS.filter(([seconds]) => seconds !== -1)) {
|
2026-11-06 08:28:04 +00:00
|
|
|
|
presets.append(h("button", {type: "button", "data-preset": seconds, on: {click: () => choosePreset(seconds)}}, name));
|
|
|
|
|
|
}
|
|
|
|
|
|
slider.addEventListener("input", () => {
|
|
|
|
|
|
stopReplay();
|
|
|
|
|
|
const span = state.now - state.first;
|
|
|
|
|
|
ui.asOf = Number(slider.value) >= 1000 ? null : state.first + (span * Number(slider.value)) / 1000;
|
|
|
|
|
|
reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
play.addEventListener("click", () => (ui.playing ? stopReplay() : startReplay()));
|
|
|
|
|
|
liveButton.addEventListener("click", () => {
|
|
|
|
|
|
stopReplay();
|
|
|
|
|
|
ui.asOf = null;
|
|
|
|
|
|
if (ui.preset === -1) {
|
|
|
|
|
|
ui.preset = DAY;
|
|
|
|
|
|
}
|
|
|
|
|
|
reload();
|
|
|
|
|
|
});
|
2026-11-06 22:20:06 +00:00
|
|
|
|
bar.append(summary, h("div", {class: "menu-box end"}, h("h3", {}, "Zeitraum"), presets, h("h3", {}, "Wiedergabe"), h("div", {class: "replay"}, play, slider, liveButton), label,
|
|
|
|
|
|
h("p", {class: "muted"}, "Einen eigenen Zeitraum setzt Filter ▾ im Stream.")));
|
|
|
|
|
|
timeDom = {summary, presets, slider, play, live: liveButton, label};
|
2026-11-06 08:28:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function reload() {
|
|
|
|
|
|
load();
|
|
|
|
|
|
if (ui.screen === "stream") {
|
|
|
|
|
|
fetchEvents();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function choosePreset(seconds) {
|
|
|
|
|
|
stopReplay();
|
|
|
|
|
|
ui.preset = seconds;
|
|
|
|
|
|
reload();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function drawTime() {
|
|
|
|
|
|
if (!timeDom) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const button of timeDom.presets.children) {
|
|
|
|
|
|
button.setAttribute("aria-pressed", String(Number(button.dataset.preset) === ui.preset));
|
|
|
|
|
|
}
|
2026-11-06 22:20:06 +00:00
|
|
|
|
timeDom.summary.textContent = `${(PRESETS.find(([seconds]) => seconds === ui.preset) || [0, "eigener"])[1]}${ui.asOf === null ? "" : " · Wiedergabe"} ▾`;
|
2026-11-06 08:28:04 +00:00
|
|
|
|
timeDom.slider.disabled = ui.preset === -1;
|
|
|
|
|
|
timeDom.play.disabled = ui.preset === -1;
|
|
|
|
|
|
timeDom.live.disabled = live();
|
|
|
|
|
|
const span = Math.max(state.now - state.first, 1);
|
|
|
|
|
|
if (!ui.playing) {
|
|
|
|
|
|
timeDom.slider.value = String(ui.asOf === null ? 1000 : Math.round((1000 * (ui.asOf - state.first)) / span));
|
|
|
|
|
|
}
|
|
|
|
|
|
timeDom.label.textContent = `${absolute.format(stampOf(state.since))} – ${absolute.format(stampOf(state.until))}${live() ? " · live" : ""}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function startReplay() {
|
|
|
|
|
|
if (ui.preset === -1) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const began = state.first;
|
|
|
|
|
|
const span = Math.max(state.now - began, 1);
|
|
|
|
|
|
const resume = ui.asOf !== null && ui.asOf < state.now - 60;
|
|
|
|
|
|
let progress = resume ? (ui.asOf - began) / span : 0;
|
|
|
|
|
|
ui.playing = true;
|
|
|
|
|
|
timeDom.play.textContent = "❚❚ Pause";
|
|
|
|
|
|
const advance = async () => {
|
|
|
|
|
|
if (!ui.playing) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
progress = Math.min(progress + 1 / 48, 1);
|
|
|
|
|
|
ui.asOf = progress >= 1 ? null : began + span * progress;
|
|
|
|
|
|
timeDom.slider.value = String(Math.round(progress * 1000));
|
|
|
|
|
|
await load();
|
|
|
|
|
|
if (ui.screen === "stream") {
|
|
|
|
|
|
fetchEvents();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (progress >= 1) {
|
|
|
|
|
|
stopReplay();
|
|
|
|
|
|
} else if (ui.playing) {
|
|
|
|
|
|
replay = window.setTimeout(advance, 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
advance();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stopReplay() {
|
|
|
|
|
|
ui.playing = false;
|
|
|
|
|
|
window.clearTimeout(replay);
|
|
|
|
|
|
if (timeDom) {
|
|
|
|
|
|
timeDom.play.textContent = "▶ Wiedergabe";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|