This commit is contained in:
Mateusz Gruszczyński
2026-09-03 09:12:27 +02:00
parent 93d93eca91
commit 14782c86aa
21 changed files with 8793 additions and 82 deletions
+64 -8
View File
@@ -134,22 +134,71 @@ function timeLabel(ts, hours) {
return new Date(ts).toLocaleString(locale(), options);
}
function chartCardIsFullscreen(card) {
return !!card && card.classList.contains('chart-fullscreen-fallback');
}
function updateChartFullscreenButton(card) {
if (!card) return;
const button = card.querySelector('[data-chart-fullscreen]');
if (!button) return;
const active = chartCardIsFullscreen(card);
button.textContent = active ? '×' : '⛶';
button.title = tr(active ? 'history.exitFullscreen' : 'history.fullscreen');
button.setAttribute('aria-label', button.title);
}
function closeChartPreview(card) {
if (!card) return;
card.classList.remove('chart-fullscreen-fallback');
document.body.classList.remove('chart-fullscreen-open');
updateChartFullscreenButton(card);
const canvas = card.querySelector('canvas[id]');
if (canvas?.id) requestAnimationFrame(() => redrawHistoryChart(canvas.id));
}
function toggleChartFullscreen(id) {
const canvas = document.getElementById(id);
const card = canvas?.closest('.history-chart-card');
if (!card) return;
const active = chartCardIsFullscreen(card);
const opened = document.querySelector('.history-chart-card.chart-fullscreen-fallback');
if (opened && opened !== card) closeChartPreview(opened);
if (active) {
closeChartPreview(card);
return;
}
card.classList.add('chart-fullscreen-fallback');
document.body.classList.add('chart-fullscreen-open');
updateChartFullscreenButton(card);
requestAnimationFrame(() => redrawHistoryChart(id));
}
function prepareCanvas(canvas, height) {
const rect = canvas.getBoundingClientRect();
const wrapWidth = Math.floor(canvas.parentElement?.clientWidth || rect.width || 720);
const wrap = canvas.parentElement;
const wrapWidth = Math.floor(wrap?.clientWidth || rect.width || 720);
const card = canvas.closest('.history-chart-card');
const fullscreenHeight = chartCardIsFullscreen(card) ? Math.floor(wrap?.clientHeight || 0) : 0;
const actualHeight = Math.max(height, fullscreenHeight || 0);
const zoom = clamp(Number(canvas.dataset.chartZoom || 1), 1, 4);
const width = Math.max(720, Math.floor(Math.max(720, wrapWidth) * zoom));
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr; canvas.height = height * dpr;
canvas.style.width = `${width}px`; canvas.style.height = `${height}px`;
canvas.width = width * dpr; canvas.height = actualHeight * dpr;
canvas.style.width = `${width}px`; canvas.style.height = `${actualHeight}px`;
const ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, width, height);
return { ctx, width, height };
ctx.clearRect(0, 0, width, actualHeight);
return { ctx, width, height:actualHeight };
}
function drawEmptyChart(canvas, height = 340) {
if (!canvas) return;
const { ctx, width } = prepareCanvas(canvas, height);
const prepared = prepareCanvas(canvas, height);
const { ctx, width } = prepared;
height = prepared.height;
ctx.fillStyle = cssColor('--muted', '#888'); ctx.font = '13px system-ui'; ctx.textAlign = 'center';
ctx.fillText(tr('history.noData'), width / 2, height / 2);
const wrap = canvas.parentElement;
@@ -392,7 +441,9 @@ function drawLineChart(canvas, series, rows, { height = 340, minValue = null, ma
const visibleSeries = series.filter((item, index) => !isChartSeriesHidden(canvas.id, item, index));
if (!rows.length || !visibleSeries.length) return drawEmptyChart(canvas, height);
const sortedRows = [...rows].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
const { ctx, width } = prepareCanvas(canvas, height);
const prepared = prepareCanvas(canvas, height);
const { ctx, width } = prepared;
height = prepared.height;
const text = cssColor('--muted', '#888'), grid = cssColor('--grid', '#333');
const pad = { left: 54, right: 20, top: 20, bottom: 42 };
const allValues = [];
@@ -445,10 +496,15 @@ function renderLegend(host, series) {
function historyChartMarkup(id, title, hint, compact = false) {
const zoom = clamp(Number(app.chartZooms[id] || 1), 1, MAX_CHART_ZOOM);
const percent = Math.round(zoom * 100);
return `<div class="panel chart-panel history-chart-card"><div class="chart-title-row"><div><h3>${esc(title)}</h3><p>${esc(hint || '')}</p><small class="chart-hover-hint">${esc(tr('history.hoverHint'))}</small></div><div class="chart-zoom-controls" aria-label="${esc(tr('history.zoomControls'))}"><button type="button" data-chart-zoom="out" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomOut'))}" ${zoom <= 1 ? 'disabled' : ''}></button><button type="button" class="chart-zoom-reset" data-chart-zoom="reset" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomReset'))}">${percent}%</button><button type="button" data-chart-zoom="in" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomIn'))}" ${zoom >= MAX_CHART_ZOOM ? 'disabled' : ''}>+</button></div></div><div class="chart-wrap ${compact ? 'compact-chart' : ''}"><canvas id="${esc(id)}" data-chart-zoom="${zoom}" width="1000" height="${compact ? 300 : 420}" tabindex="0" aria-label="${esc(title)}"></canvas><div class="chart-hover-line" hidden></div><div class="chart-selection" hidden></div><div class="chart-tooltip" hidden></div></div><div class="legend" id="${esc(id)}Legend"></div></div>`;
return `<div class="panel chart-panel history-chart-card"><div class="chart-title-row"><div><h3>${esc(title)}</h3><p>${esc(hint || '')}</p><small class="chart-hover-hint">${esc(tr('history.hoverHint'))}</small></div><div class="chart-title-actions"><div class="chart-zoom-controls" aria-label="${esc(tr('history.zoomControls'))}"><button type="button" data-chart-zoom="out" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomOut'))}" ${zoom <= 1 ? 'disabled' : ''}></button><button type="button" class="chart-zoom-reset" data-chart-zoom="reset" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomReset'))}">${percent}%</button><button type="button" data-chart-zoom="in" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomIn'))}" ${zoom >= MAX_CHART_ZOOM ? 'disabled' : ''}>+</button></div><button type="button" class="chart-fullscreen-button" data-chart-fullscreen="${esc(id)}" title="${esc(tr('history.fullscreen'))}" aria-label="${esc(tr('history.fullscreen'))}">⛶</button></div></div><div class="chart-wrap ${compact ? 'compact-chart' : ''}"><canvas id="${esc(id)}" data-chart-zoom="${zoom}" width="1000" height="${compact ? 300 : 420}" tabindex="0" aria-label="${esc(title)}"></canvas><div class="chart-hover-line" hidden></div><div class="chart-selection" hidden></div><div class="chart-tooltip" hidden></div></div><div class="legend" id="${esc(id)}Legend"></div></div>`;
}
function historySeriesColor(index) {
return cssColor(HISTORY_COLORS[index % HISTORY_COLORS.length], `hsl(${(index * 67) % 360} 68% 55%)`);
}
document.addEventListener('keydown', event => {
if (event.key === 'Escape') closeChartPreview(document.querySelector('.history-chart-card.chart-fullscreen-fallback'));
});