webapp: keep a tile's Log tail open across refreshes

Each SSE/poll refresh replaces the tiles' HTML, so the nested Log tail
<details> snapped shut on every update while a wipe was running. Its
open state is now remembered per tile in localStorage like the tile
itself, and its scroll position is kept: following the bottom keeps
following new lines, scrolled up stays put.
This commit is contained in:
cproudlock
2026-10-05 14:02:51 -04:00
parent 8c36d1ba5d
commit 6fcdc8ddb5
2 changed files with 36 additions and 6 deletions

View File

@@ -200,9 +200,9 @@
{% endif %}
{% if s.log_tail %}
<details class="mt-3">
<details class="mt-3 imaging-log" data-key="{{ card_key }}">
<summary class="eyebrow" style="cursor:pointer;">{% if is_inferred %}Evidence{% else %}Log tail{% endif %} ({{ s.log_tail | length }} line{{ 's' if s.log_tail | length != 1 }})</summary>
<pre class="mono p-2 mt-2 mb-0" style="max-height: 12rem; overflow-y: auto; background:var(--bg); border:1px solid var(--border); border-radius:0.25rem; font-size:0.78rem;">{% for line in s.log_tail %}{{ line }}
<pre class="mono p-2 mt-2 mb-0 imaging-log-pre" style="max-height: 12rem; overflow-y: auto; background:var(--bg); border:1px solid var(--border); border-radius:0.25rem; font-size:0.78rem;">{% for line in s.log_tail %}{{ line }}
{% endfor %}</pre>
</details>
{% endif %}

View File

@@ -321,14 +321,18 @@ document.addEventListener('keydown', function(e) {
// it replaces the innerHTML of #imaging-tiles.
(function() {
var EXPANDED_KEY = 'imaging-expanded';
function loadExpandedSet() {
try { return new Set(JSON.parse(localStorage.getItem(EXPANDED_KEY) || '[]')); }
var LOG_EXPANDED_KEY = 'imaging-log-expanded';
function loadExpandedSet(key) {
try { return new Set(JSON.parse(localStorage.getItem(key || EXPANDED_KEY) || '[]')); }
catch (e) { return new Set(); }
}
function saveExpandedSet(set) {
try { localStorage.setItem(EXPANDED_KEY, JSON.stringify(Array.from(set))); }
function saveExpandedSet(set, key) {
try { localStorage.setItem(key || EXPANDED_KEY, JSON.stringify(Array.from(set))); }
catch (e) {}
}
// Log-tail scroll per tile, in memory only. A tile followed at the
// bottom keeps following new lines; one scrolled up stays put.
var logScroll = {};
function restoreExpandedState() {
var expanded = loadExpandedSet();
@@ -344,6 +348,32 @@ document.addEventListener('keydown', function(e) {
card._toggleBound = true;
}
});
// The nested "Log tail" <details> is re-rendered closed on every
// refresh; keep it open (and its scroll) the same way as the tile.
var logExpanded = loadExpandedSet(LOG_EXPANDED_KEY);
document.querySelectorAll('.imaging-log').forEach(function(log) {
var key = log.getAttribute('data-key');
if (!key) return;
if (logExpanded.has(key)) log.open = true;
var pre = log.querySelector('.imaging-log-pre');
if (pre) {
var saved = logScroll[key];
if (!saved || saved.atBottom) pre.scrollTop = pre.scrollHeight;
else pre.scrollTop = saved.top;
pre.addEventListener('scroll', function() {
logScroll[key] = {
top: pre.scrollTop,
atBottom: pre.scrollTop + pre.clientHeight >= pre.scrollHeight - 4
};
});
}
log.addEventListener('toggle', function() {
var s = loadExpandedSet(LOG_EXPANDED_KEY);
if (log.open) s.add(key); else s.delete(key);
saveExpandedSet(s, LOG_EXPANDED_KEY);
if (log.open && pre) pre.scrollTop = pre.scrollHeight;
});
});
}
function autoRenderLapsQRs() {