This commit is contained in:
Mateusz Gruszczyński
2026-09-16 22:22:09 +02:00
parent c7d47db64e
commit bb39ab9dfa
27 changed files with 523 additions and 319 deletions
+86 -5
View File
@@ -3,6 +3,7 @@
const PUBLIC_CHART_COLORS = ['--accent', '--info', '--warning', '--purple', '--danger', '--teal', '--orange', '--blue'];
const $ = selector => document.querySelector(selector);
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
const PUBLIC_CHART_RANGES = [6, 24, 168, 720, 2160, 8760];
function basePathFromScript() {
const src = document.currentScript?.src || '';
@@ -41,7 +42,7 @@ function preciseTime(timestamp, locale) {
function prepareCanvas(canvas, height = 520) {
const wrap = canvas.parentElement;
const width = Math.max(720, Math.floor(wrap?.clientWidth || canvas.getBoundingClientRect().width || 720));
const actualHeight = Math.max(320, Math.floor(wrap?.clientHeight || height));
const actualHeight = Math.max(180, Math.floor(wrap?.clientHeight || height));
const dpr = window.devicePixelRatio || 1;
canvas.width = Math.floor(width * dpr);
canvas.height = Math.floor(actualHeight * dpr);
@@ -194,12 +195,64 @@ function drawChart(canvas, payload, locale) {
bindTooltip(canvas, series, { pad, width, height, firstTs, lastTs }, locale);
}
async function loadPublicChart() {
const token = decodeURIComponent(location.pathname.split('/').filter(Boolean).pop() || '');
let publicChartPayload = null;
let publicChartLocale = 'en-GB';
let publicChartResizeFrame = 0;
function publicChartToken() {
return decodeURIComponent(location.pathname.split('/').filter(Boolean).pop() || '');
}
function requestedPublicChartHours() {
const value = Number(new URLSearchParams(location.search).get('hours'));
return Number.isInteger(value) && value >= 1 && value <= 87600 ? value : null;
}
function setPublicRangeLanguage(lang) {
const pl = lang === 'pl';
const label = $('#publicChartRangeLabel');
const select = $('#publicChartHours');
if (label) label.textContent = pl ? 'Zakres' : 'Range';
if (!select) return;
const labels = pl
? ['6 godzin', '24 godziny', '7 dni', '30 dni', '90 dni', '1 rok']
: ['6 hours', '24 hours', '7 days', '30 days', '90 days', '1 year'];
[...select.options].forEach((option, index) => { if (labels[index]) option.textContent = labels[index]; });
select.setAttribute('aria-label', pl ? 'Zakres wykresu' : 'Chart range');
}
function syncPublicRangeControl(hours) {
const select = $('#publicChartHours');
if (!select) return;
const value = String(hours);
if (![...select.options].some(option => option.value === value)) {
const option = document.createElement('option');
option.value = value;
option.textContent = `${value} h`;
select.append(option);
}
select.value = value;
}
function publicChartApiPath(token, hours) {
const value = Number(hours);
const hasHours = hours !== null && hours !== undefined && Number.isInteger(value) && value >= 1 && value <= 87600;
const query = hasHours ? `?hours=${encodeURIComponent(String(value))}` : '';
return `/api/public/charts/custom/${encodeURIComponent(token)}${query}`;
}
function redrawPublicChart() {
if (!publicChartPayload) return;
drawChart($('#publicCustomChart'), publicChartPayload, publicChartLocale);
}
async function loadPublicChart(hours = requestedPublicChartHours()) {
const token = publicChartToken();
const title = $('#publicChartTitle');
const hint = $('#publicChartHint');
const error = $('#publicChartError');
const canvas = $('#publicCustomChart');
const range = $('#publicChartHours');
if (!token.startsWith('chart_')) {
title.textContent = 'Custom chart';
@@ -210,7 +263,9 @@ async function loadPublicChart() {
return;
}
const response = await fetch(withBase(`/api/public/charts/custom/${encodeURIComponent(token)}`), { headers: { Accept: 'application/json' }, cache: 'no-store' });
if (range) range.disabled = true;
error.hidden = true;
const response = await fetch(withBase(publicChartApiPath(token, hours)), { headers: { Accept: 'application/json' }, cache: 'no-store' });
if (!response.ok) {
let message = `${response.status}`;
try { message = (await response.json()).error || message; } catch (_) { }
@@ -223,14 +278,40 @@ async function loadPublicChart() {
title.textContent = payload.title || (lang === 'pl' ? 'Wykres niestandardowy' : 'Custom chart');
hint.textContent = payload.hint || '';
canvas.setAttribute('aria-label', title.textContent);
setPublicRangeLanguage(lang);
syncPublicRangeControl(payload.hours);
publicChartPayload = payload;
publicChartLocale = locale;
drawChart(canvas, payload, locale);
window.addEventListener('resize', () => drawChart(canvas, payload, locale));
if (range) range.disabled = false;
}
$('#publicChartHours')?.addEventListener('change', async event => {
const hours = Number(event.currentTarget.value);
if (!Number.isInteger(hours) || hours < 1 || hours > 87600) return;
const url = new URL(location.href);
url.searchParams.set('hours', String(hours));
history.replaceState(null, '', url);
try {
await loadPublicChart(hours);
} catch (error) {
const host = $('#publicChartError');
host.hidden = false;
host.textContent = `${document.documentElement.lang === 'pl' ? 'Nie udało się wczytać wykresu' : 'Unable to load chart'}: ${error.message}`;
event.currentTarget.disabled = false;
}
});
window.addEventListener('resize', () => {
cancelAnimationFrame(publicChartResizeFrame);
publicChartResizeFrame = requestAnimationFrame(redrawPublicChart);
});
loadPublicChart().catch(error => {
const host = $('#publicChartError');
host.hidden = false;
host.textContent = `Unable to load chart: ${error.message}`;
$('#publicChartHint').textContent = '';
$('#publicChartHours').disabled = false;
drawEmpty($('#publicCustomChart'), host.textContent);
});