v0.14.8
This commit is contained in:
+70
-5
@@ -66,29 +66,36 @@ function resetHistoryRangeControl(host) {
|
||||
if (range && toolbar && range.parentElement === host) toolbar.insertBefore(range, refresh);
|
||||
}
|
||||
|
||||
function configureHistoryRangeControl(tab, host) {
|
||||
function configureHistoryRangeControl(tab, host, hasEnergyTargets = true) {
|
||||
const range = $('#historyRangeControl');
|
||||
if (!range) return;
|
||||
const toolbar = range.closest('.chart-toolbar') || host?.closest('.chart-toolbar');
|
||||
const toolbarPanel = toolbar?.closest('.history-toolbar-panel');
|
||||
const refresh = $('#historyRefresh');
|
||||
const label = range.querySelector('span');
|
||||
const hint = $('#historyRangeHint');
|
||||
const sixHours = range.querySelector('option[value="6"]');
|
||||
const energy = tab === 'energy';
|
||||
const energyUnavailable = energy && !hasEnergyTargets;
|
||||
const rangeLabel = tr(energy ? 'energy.period' : 'history.range');
|
||||
if (label) label.textContent = rangeLabel;
|
||||
range.setAttribute('aria-label', rangeLabel);
|
||||
if (hint) { hint.textContent = energy ? tr('energy.periodHint') : ''; hint.hidden = !energy; }
|
||||
if (sixHours) { sixHours.hidden = energy; sixHours.disabled = energy; }
|
||||
if (energy && $('#historyHours')?.value === '6') $('#historyHours').value = '24';
|
||||
range.hidden = energyUnavailable;
|
||||
if (refresh) refresh.hidden = energyUnavailable;
|
||||
if (toolbarPanel) toolbarPanel.hidden = energyUnavailable;
|
||||
range.classList.toggle('energy-period-control', energy);
|
||||
if (toolbar) toolbar.classList.toggle('energy-toolbar', energy);
|
||||
if (energy && host) host.appendChild(range);
|
||||
if (energy && host && hasEnergyTargets) host.appendChild(range);
|
||||
}
|
||||
|
||||
function renderHistoryNavigation() {
|
||||
$$('#historyTabs [data-history-tab]').forEach(button => button.classList.toggle('active', button.dataset.historyTab === app.historyTab));
|
||||
const energyPickerWasOpen = $('#historyEnergyTargetPicker')?.open === true;
|
||||
const host = $('#historyContextControls'); if (!host) return;
|
||||
let hasEnergyTargets = true;
|
||||
resetHistoryRangeControl(host);
|
||||
host.classList.toggle('energy-context-controls', app.historyTab === 'energy');
|
||||
const options = historyEntityOptions();
|
||||
@@ -100,10 +107,11 @@ function renderHistoryNavigation() {
|
||||
const select = $('#historyDeviceSelect'); if ([...select.options].some(option => option.value === app.historyDevice)) select.value = app.historyDevice;
|
||||
} else if (app.historyTab === 'energy') {
|
||||
const targets = normalizeEnergyHistoryTargets();
|
||||
hasEnergyTargets = targets.length > 0;
|
||||
const targetOptions = targets.map(target => `<label class="history-energy-option"><input type="checkbox" data-history-energy-target="${esc(target.id)}" ${app.historyEnergyTargets.includes(target.id) ? 'checked' : ''}><span>${esc(target.label)}</span></label>`).join('');
|
||||
host.innerHTML = targets.length
|
||||
? `<div class="history-energy-targets"><span class="history-control-label">${esc(tr('energy.targets'))}</span><details class="history-energy-picker" id="historyEnergyTargetPicker"><summary><span>${esc(energyTargetPickerSummary(targets))}</span><b>${app.historyEnergyTargets.length}/8</b></summary><div class="history-energy-options">${targetOptions}</div></details><small>${esc(tr('energy.multiselectHint'))}</small></div><label><span>${esc(tr('history.bucket'))}</span><select id="historyEnergyInterval"><option value="hourly">${esc(tr('energy.hourly'))}</option><option value="daily">${esc(tr('energy.daily'))}</option><option value="weekly">${esc(tr('energy.weekly'))}</option><option value="monthly">${esc(tr('energy.monthly'))}</option></select></label><label><span>${esc(tr('energy.compare'))}</span><select id="historyEnergyCompare"><option value="none">${esc(tr('energy.compareNone'))}</option><option value="previous_day">${esc(tr('energy.comparePreviousDay'))}</option><option value="previous_period">${esc(tr('energy.comparePreviousPeriod'))}</option><option value="previous_year">${esc(tr('energy.comparePreviousYear'))}</option></select></label>`
|
||||
: `<span class="history-context-hint">${esc(tr('energy.noData'))}</span>`;
|
||||
: '';
|
||||
const intervalSelect = $('#historyEnergyInterval'); if (intervalSelect) intervalSelect.value = app.historyEnergyInterval;
|
||||
const compareSelect = $('#historyEnergyCompare'); if (compareSelect) compareSelect.value = app.historyEnergyCompare;
|
||||
if (energyPickerWasOpen && $('#historyEnergyTargetPicker')) $('#historyEnergyTargetPicker').open = true;
|
||||
@@ -121,7 +129,7 @@ function renderHistoryNavigation() {
|
||||
} else {
|
||||
host.innerHTML = `<span class="history-context-hint">${esc(tr('history.overviewHint'))}</span>`;
|
||||
}
|
||||
configureHistoryRangeControl(app.historyTab, host);
|
||||
configureHistoryRangeControl(app.historyTab, host, hasEnergyTargets);
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
@@ -164,6 +172,7 @@ async function loadHistory() {
|
||||
const HISTORY_COLORS = ['--accent', '--info', '--warning', '--purple', '--danger', '--teal', '--orange', '--blue'];
|
||||
const MAX_CHART_ZOOM = 16;
|
||||
const chartRuntime = new Map();
|
||||
const chartFullscreenState = new WeakMap();
|
||||
|
||||
function chartSeriesKey(item, index) {
|
||||
return item.key || `${index}:${item.label}`;
|
||||
@@ -251,6 +260,28 @@ function updateChartFullscreenButton(card) {
|
||||
button.setAttribute('aria-label', button.title);
|
||||
}
|
||||
|
||||
function restoreChartPreviewLayout(card) {
|
||||
const state = chartFullscreenState.get(card);
|
||||
if (!state) return null;
|
||||
const canvas = card.querySelector('canvas[id]');
|
||||
const wrap = canvas?.parentElement;
|
||||
if (canvas) {
|
||||
if (state.canvasStyleWidth) canvas.style.width = state.canvasStyleWidth; else canvas.style.removeProperty('width');
|
||||
if (state.canvasStyleHeight) canvas.style.height = state.canvasStyleHeight; else canvas.style.removeProperty('height');
|
||||
canvas.width = state.canvasWidth;
|
||||
canvas.height = state.canvasHeight;
|
||||
}
|
||||
if (wrap) {
|
||||
if (state.wrapStyleHeight) wrap.style.height = state.wrapStyleHeight; else wrap.style.removeProperty('height');
|
||||
if (state.wrapStyleMinHeight) wrap.style.minHeight = state.wrapStyleMinHeight; else wrap.style.removeProperty('min-height');
|
||||
if (state.wrapStyleMaxHeight) wrap.style.maxHeight = state.wrapStyleMaxHeight; else wrap.style.removeProperty('max-height');
|
||||
wrap.scrollLeft = state.scrollLeft;
|
||||
wrap.scrollTop = state.scrollTop;
|
||||
}
|
||||
chartFullscreenState.delete(card);
|
||||
return state;
|
||||
}
|
||||
|
||||
function closeChartPreview(card) {
|
||||
if (!card) return;
|
||||
card.classList.remove('chart-fullscreen-fallback');
|
||||
@@ -258,9 +289,31 @@ function closeChartPreview(card) {
|
||||
card.removeAttribute('aria-modal');
|
||||
card.removeAttribute('aria-label');
|
||||
document.body.classList.remove('chart-fullscreen-open');
|
||||
const state = restoreChartPreviewLayout(card);
|
||||
updateChartFullscreenButton(card);
|
||||
const canvas = card.querySelector('canvas[id]');
|
||||
if (canvas?.id) requestAnimationFrame(() => redrawHistoryChart(canvas.id));
|
||||
if (!canvas?.id) return;
|
||||
|
||||
// Force layout after dropping the fullscreen class before sizing the canvas again.
|
||||
// This prevents a fullscreen-sized canvas from keeping a dialog artificially tall.
|
||||
card.getBoundingClientRect();
|
||||
requestAnimationFrame(() => {
|
||||
redrawHistoryChart(canvas.id);
|
||||
const wrap = canvas.parentElement;
|
||||
if (wrap && state) {
|
||||
wrap.scrollLeft = state.scrollLeft;
|
||||
wrap.scrollTop = state.scrollTop;
|
||||
}
|
||||
const dialog = card.closest('dialog');
|
||||
if (dialog?.open) {
|
||||
dialog.style.removeProperty('height');
|
||||
dialog.style.removeProperty('max-height');
|
||||
requestAnimationFrame(() => {
|
||||
dialog.style.removeProperty('height');
|
||||
dialog.style.removeProperty('max-height');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleChartFullscreen(id) {
|
||||
@@ -278,6 +331,18 @@ function toggleChartFullscreen(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrap = canvas.parentElement;
|
||||
chartFullscreenState.set(card, {
|
||||
canvasStyleWidth: canvas.style.width,
|
||||
canvasStyleHeight: canvas.style.height,
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
wrapStyleHeight: wrap?.style.height || '',
|
||||
wrapStyleMinHeight: wrap?.style.minHeight || '',
|
||||
wrapStyleMaxHeight: wrap?.style.maxHeight || '',
|
||||
scrollLeft: wrap?.scrollLeft || 0,
|
||||
scrollTop: wrap?.scrollTop || 0,
|
||||
});
|
||||
card.classList.add('chart-fullscreen-fallback');
|
||||
card.setAttribute('role', 'dialog');
|
||||
card.setAttribute('aria-modal', 'true');
|
||||
|
||||
Reference in New Issue
Block a user