Skip to content

Commit c90c4b0

Browse files
dkulpclaude
andcommitted
fix(ui): stop the header sensor tooltip from blinking on every refresh
The header sensor cache key included the sensor readings, so every status refresh rebuilt the #header_sensors markup and re-created the Bootstrap tooltips. An open tooltip therefore closed and reopened once a second. Key the rebuild on the sensor structure (icons + labels) instead. Tooltips are created once with a function title that Bootstrap resolves at show time, value-only updates just rewrite the text of the existing spans, and an open tooltip has its inner content patched in place so it keeps tracking the live readings. Old tooltip instances are now disposed when a rebuild does happen. Also fixes the rotated-to sensor being reset back to the saved default by the next refresh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6a07e63 commit c90c4b0

1 file changed

Lines changed: 87 additions & 29 deletions

File tree

www/js/fpp.js

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13478,58 +13478,90 @@ function RefreshHeaderBar () {
1347813478
}
1347913479

1348013480
if (data.sensors != undefined) {
13481-
var nonFanSensors = data.sensors.filter(function(s) {
13481+
var nonFanSensors = data.sensors.filter(function (s) {
1348213482
return s.valueType !== 'FanSpeed';
1348313483
});
13484-
var sensors = [];
1348513484
var tooltip = '';
1348613485
// Tooltip shows all sensors (including fans)
1348713486
data.sensors.forEach(function (e) {
1348813487
var tv = e.formatted;
13489-
if (e.valueType === 'Temperature' && typeof temperatureUnit !== 'undefined' && temperatureUnit) {
13488+
if (
13489+
e.valueType === 'Temperature' &&
13490+
typeof temperatureUnit !== 'undefined' &&
13491+
temperatureUnit
13492+
) {
1349013493
tv = (parseFloat(e.value) * 1.8 + 32).toFixed(2) + '&deg;F';
1349113494
}
1349213495
tooltip += '<b>' + e.label + '</b>' + tv + '<br/>';
1349313496
});
13497+
// The tooltip text changes on every refresh. Bootstrap resolves a
13498+
// function title at show time, so stash the current text here and the
13499+
// tooltip objects never have to be recreated. Recreating them is what
13500+
// used to make an open tooltip blink away and back once a second.
13501+
headerCache.SensorTooltip = tooltip;
13502+
1349413503
// Header rotating display excludes fan speed sensors
13504+
var sensorIcons = [];
13505+
var sensorLabels = [];
13506+
var sensorValues = [];
1349513507
nonFanSensors.forEach(function (e) {
1349613508
var icon = 'bolt';
1349713509
var val = e.formatted;
1349813510
if (e.valueType == 'Temperature') {
1349913511
icon = 'thermometer-half';
1350013512
if (typeof temperatureUnit !== 'undefined' && temperatureUnit) {
13501-
val = val * 1.8 + 32;
13502-
val = parseFloat(val).toFixed(2);
13513+
val = parseFloat(val * 1.8 + 32).toFixed(2);
1350313514
val += '&deg;F';
1350413515
} else {
1350513516
val += '&deg;C';
1350613517
}
1350713518
}
13508-
row =
13509-
'<span class="sensorSpan hiddenSensor" onclick="RotateHeaderSensor(' +
13510-
(sensors.length + 1) +
13511-
')" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true" data-sensorcount="' +
13512-
sensors.length +
13513-
'" class="hiddenSensor" data-bs-title="TOOLTIP_DETAILS"><i class="fas fa-' +
13514-
icon +
13515-
'"></i><small>' +
13516-
e.label +
13517-
val +
13518-
'</small></span>';
13519-
sensors.push(row);
13519+
sensorIcons.push(icon);
13520+
sensorLabels.push(e.label);
13521+
sensorValues.push(e.label + val);
1352013522
});
13521-
var sensorsJoined = sensors.join('');
13522-
sensorsJoined = sensorsJoined.replace(/TOOLTIP_DETAILS/g, tooltip);
13523-
if (headerCache.Sensors != sensorsJoined) {
13524-
$('.sensorSpan').each(function () {
13525-
$(this).tooltip('hide');
13526-
});
13527-
$('#header_sensors').html(sensorsJoined);
13528-
$('.sensorSpan').each(function () {
13529-
$(this).tooltip();
13530-
});
13531-
headerCache.Sensors = sensorsJoined;
13532-
if (sensors.length > 1) $('#header_sensors').css('cursor', 'pointer');
13523+
13524+
// Only the readings change from one refresh to the next, so rebuild the
13525+
// spans (and the tooltips attached to them) only when the set of
13526+
// sensors itself changes.
13527+
var structure = sensorIcons.join('|') + '||' + sensorLabels.join('|');
13528+
var values = sensorValues.join('|');
13529+
if (headerCache.Sensors != structure) {
13530+
$('#header_sensors')
13531+
.find('.sensorSpan')
13532+
.each(function () {
13533+
var tip = bootstrap.Tooltip.getInstance(this);
13534+
if (tip) tip.dispose();
13535+
});
13536+
var spans = '';
13537+
for (var i = 0; i < sensorValues.length; i++) {
13538+
spans +=
13539+
'<span class="sensorSpan hiddenSensor" onclick="RotateHeaderSensor(' +
13540+
(i + 1) +
13541+
')" data-sensorcount="' +
13542+
i +
13543+
'"><i class="fas fa-' +
13544+
sensorIcons[i] +
13545+
'"></i><small>' +
13546+
sensorValues[i] +
13547+
'</small></span>';
13548+
}
13549+
$('#header_sensors').html(spans);
13550+
$('#header_sensors')
13551+
.find('.sensorSpan')
13552+
.each(function () {
13553+
new bootstrap.Tooltip(this, {
13554+
placement: 'bottom',
13555+
html: true,
13556+
title: function () {
13557+
return headerCache.SensorTooltip;
13558+
}
13559+
});
13560+
});
13561+
headerCache.Sensors = structure;
13562+
headerCache.SensorValues = values;
13563+
if (sensorValues.length > 1)
13564+
$('#header_sensors').css('cursor', 'pointer');
1353313565
if (
1353413566
$('#header_sensors').data('defaultsensor') != undefined &&
1353513567
Number.isInteger($('#header_sensors').data('defaultsensor'))
@@ -13538,7 +13570,33 @@ function RefreshHeaderBar () {
1353813570
} else {
1353913571
RotateHeaderSensor(0);
1354013572
}
13573+
} else if (headerCache.SensorValues != values) {
13574+
// Same sensors, new readings. Update the text in place so the
13575+
// tooltips (and which sensor is currently rotated into view) are
13576+
// left alone.
13577+
$('#header_sensors')
13578+
.find('.sensorSpan')
13579+
.each(function (i) {
13580+
if (i < sensorValues.length) {
13581+
$(this).find('small').html(sensorValues[i]);
13582+
}
13583+
});
13584+
headerCache.SensorValues = values;
1354113585
}
13586+
13587+
// If a tooltip is open right now, refresh its text in place rather than
13588+
// letting it close and reopen.
13589+
$('#header_sensors')
13590+
.find('.sensorSpan[aria-describedby]')
13591+
.each(function () {
13592+
var tip = document.getElementById(
13593+
this.getAttribute('aria-describedby')
13594+
);
13595+
var inner = tip ? tip.querySelector('.tooltip-inner') : null;
13596+
if (inner && inner.innerHTML != tooltip) {
13597+
inner.innerHTML = tooltip;
13598+
}
13599+
});
1354213600
}
1354313601

1354413602
if (data.timeStr != undefined && data.dateStr != undefined) {

0 commit comments

Comments
 (0)