ui: auto-refresh the proactive log and STK menu state
The proactive command list only re-rendered on tab/pill switches, so new fetched commands (background STATUS polling, menu traffic) stayed invisible while the Phone view was open. - /api/status now exposes proactive_seq (_PROACTIVE_ENTRY_ID, monotonic and not reset with the log), so the existing 2s status poll detects changes with no extra request; pysimCardStateUpdate() re-renders the log when the sequence changed and the Phone/Phone view is visible - the 5s backend timer no longer fetches the log (it was discarded) and now uses the already-fetched stk-status: when active/pending changes while the Phone tab is visible, stkCheckMenu() refreshes the menu button without a tab switch - pysimProactiveLogRender() keeps its scroll position and only shows Loading... on a first/empty paint - tests: proactive seq + STK signature helpers and poll-driven render behaviour in card_state.test.js; SW cache v123 -> v124.
This commit is contained in:
+27
-3
@@ -6713,6 +6713,10 @@ function pysimCardStateUpdate(status) {
|
|||||||
_pysimCardEquipped = !!status.connected;
|
_pysimCardEquipped = !!status.connected;
|
||||||
_pysimEquipping = !!status.equipping;
|
_pysimEquipping = !!status.equipping;
|
||||||
pysimApplyAvailability();
|
pysimApplyAvailability();
|
||||||
|
if (pysimProactiveSeqChanged(status.proactive_seq)
|
||||||
|
&& isViewVisible('tab-phone') && isViewVisible('phone-sub-phone')) {
|
||||||
|
pysimProactiveLogRender();
|
||||||
|
}
|
||||||
const key = [status.connected, !!status.card_present, !!status.equipping, !!status.auto_equip, status.card_session].join('|');
|
const key = [status.connected, !!status.card_present, !!status.equipping, !!status.auto_equip, status.card_session].join('|');
|
||||||
if (key === _pysimCardStateKey) return;
|
if (key === _pysimCardStateKey) return;
|
||||||
_pysimCardStateKey = key;
|
_pysimCardStateKey = key;
|
||||||
@@ -6752,17 +6756,35 @@ function pysimStartBackendPoll() {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
_pysimPollTimer = setInterval(async () => {
|
_pysimPollTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const [log, stk, ps] = await Promise.all([
|
const [stk, ps] = await Promise.all([
|
||||||
pysimFetch('/api/proactive-log'),
|
|
||||||
pysimFetch('/api/stk-status'),
|
pysimFetch('/api/stk-status'),
|
||||||
pysimFetch('/api/poll-status'),
|
pysimFetch('/api/poll-status'),
|
||||||
]);
|
]);
|
||||||
pysimUpdatePollUI(ps.enabled, ps.interval);
|
pysimUpdatePollUI(ps.enabled, ps.interval);
|
||||||
|
if (pysimStkStatusChanged(stk) && isViewVisible('tab-phone')) stkCheckMenu();
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _pysimStkSig = null;
|
||||||
|
|
||||||
|
function pysimStkStatusChanged(stk) {
|
||||||
|
if (!stk) return false;
|
||||||
|
const sig = [!!stk.active, !!stk.pending, stk.pending_type || ''].join('|');
|
||||||
|
if (sig === _pysimStkSig) return false;
|
||||||
|
_pysimStkSig = sig;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== proactive command log =====
|
// ===== proactive command log =====
|
||||||
|
let _pysimProactiveSeq = null;
|
||||||
|
|
||||||
|
function pysimProactiveSeqChanged(seq) {
|
||||||
|
if (seq === undefined || seq === null) return false;
|
||||||
|
if (_pysimProactiveSeq === seq) return false;
|
||||||
|
_pysimProactiveSeq = seq;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const CMD_NAMES = {
|
const CMD_NAMES = {
|
||||||
'03': 'POLL INTERVAL', '05': 'SET UP EVENT LIST',
|
'03': 'POLL INTERVAL', '05': 'SET UP EVENT LIST',
|
||||||
'13': 'SEND SHORT MESSAGE', '20': 'PLAY TONE',
|
'13': 'SEND SHORT MESSAGE', '20': 'PLAY TONE',
|
||||||
@@ -6818,7 +6840,8 @@ function pysimLogFields(decoded) {
|
|||||||
|
|
||||||
async function pysimProactiveLogRender() {
|
async function pysimProactiveLogRender() {
|
||||||
const el = document.getElementById('pysim-proactive-log');
|
const el = document.getElementById('pysim-proactive-log');
|
||||||
el.innerHTML = '<span class="text-gray-400">' + t('Loading...') + '</span>';
|
const scrollTop = el.scrollTop;
|
||||||
|
if (!el.firstChild) el.innerHTML = '<span class="text-gray-400">' + t('Loading...') + '</span>';
|
||||||
try {
|
try {
|
||||||
const log = await pysimFetch('/api/proactive-log');
|
const log = await pysimFetch('/api/proactive-log');
|
||||||
if (!log || !log.length) {
|
if (!log || !log.length) {
|
||||||
@@ -6865,6 +6888,7 @@ async function pysimProactiveLogRender() {
|
|||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
});
|
});
|
||||||
el.innerHTML = html;
|
el.innerHTML = html;
|
||||||
|
el.scrollTop = scrollTop;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
el.innerHTML = '<span class="text-red-500">Error: ' + esc(err.message) + '</span>';
|
el.innerHTML = '<span class="text-red-500">Error: ' + esc(err.message) + '</span>';
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v123';
|
const CACHE = 'otaman-v124';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -22,23 +22,29 @@ function extractFunc(src, name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
||||||
+ 'var _pysimServerAvailable = null;\nvar _pysimCardEquipped = false;\n';
|
+ 'var _pysimServerAvailable = null;\nvar _pysimCardEquipped = false;\n'
|
||||||
|
+ 'var _pysimProactiveSeq = null;\nvar _pysimStkSig = null;\n';
|
||||||
code += extractFunc(html, 'pysimCardStateUpdate') + '\n';
|
code += extractFunc(html, 'pysimCardStateUpdate') + '\n';
|
||||||
code += extractFunc(html, 'pysimAvailabilityState') + '\n';
|
code += extractFunc(html, 'pysimAvailabilityState') + '\n';
|
||||||
code += extractFunc(html, 'pysimControlDisabled') + '\n';
|
code += extractFunc(html, 'pysimControlDisabled') + '\n';
|
||||||
|
code += extractFunc(html, 'pysimProactiveSeqChanged') + '\n';
|
||||||
|
code += extractFunc(html, 'pysimStkStatusChanged') + '\n';
|
||||||
code += '\nglobalThis.esc = s => s;\n';
|
code += '\nglobalThis.esc = s => s;\n';
|
||||||
code += 'globalThis.t = s => s;\n';
|
code += 'globalThis.t = s => s;\n';
|
||||||
eval(code);
|
eval(code);
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const el = { textContent: 'status line', innerHTML: '' };
|
const el = { textContent: 'status line', innerHTML: '' };
|
||||||
const calls = { connected: [], resets: [], refreshStatus: [] };
|
const calls = { connected: [], resets: [], refreshStatus: [], proactive: 0 };
|
||||||
_pysimCardStateKey = null;
|
_pysimCardStateKey = null;
|
||||||
_pysimCardSession = null;
|
_pysimCardSession = null;
|
||||||
|
_pysimProactiveSeq = null;
|
||||||
globalThis.document = { getElementById: () => el, querySelectorAll: () => [] };
|
globalThis.document = { getElementById: () => el, querySelectorAll: () => [] };
|
||||||
globalThis.pysimSetConnected = v => calls.connected.push(v);
|
globalThis.pysimSetConnected = v => calls.connected.push(v);
|
||||||
globalThis.pysimResetCardData = refresh => calls.resets.push(refresh);
|
globalThis.pysimResetCardData = refresh => calls.resets.push(refresh);
|
||||||
globalThis.pysimApplyAvailability = () => {};
|
globalThis.pysimApplyAvailability = () => {};
|
||||||
|
globalThis.isViewVisible = () => true;
|
||||||
|
globalThis.pysimProactiveLogRender = () => { calls.proactive++; };
|
||||||
return { el, calls };
|
return { el, calls };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,3 +133,38 @@ test('no card with auto-equip enabled still shows the no-card message', () => {
|
|||||||
assert.ok(el.innerHTML.includes('No card detected'), el.innerHTML);
|
assert.ok(el.innerHTML.includes('No card detected'), el.innerHTML);
|
||||||
assert.ok(!el.innerHTML.includes('initializing'), el.innerHTML);
|
assert.ok(!el.innerHTML.includes('initializing'), el.innerHTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('proactive log refreshes when the status sequence changes', () => {
|
||||||
|
const { calls } = setup();
|
||||||
|
pysimCardStateUpdate(status({ proactive_seq: 7 }));
|
||||||
|
pysimCardStateUpdate(status({ proactive_seq: 7 }));
|
||||||
|
assert.strictEqual(calls.proactive, 1);
|
||||||
|
pysimCardStateUpdate(status({ proactive_seq: 8 }));
|
||||||
|
assert.strictEqual(calls.proactive, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('proactive log is not refreshed while the phone view is hidden', () => {
|
||||||
|
const { calls } = setup();
|
||||||
|
globalThis.isViewVisible = () => false;
|
||||||
|
pysimCardStateUpdate(status({ proactive_seq: 3 }));
|
||||||
|
assert.strictEqual(calls.proactive, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pysimProactiveSeqChanged tracks the last sequence', () => {
|
||||||
|
_pysimProactiveSeq = null;
|
||||||
|
assert.ok(pysimProactiveSeqChanged(4));
|
||||||
|
assert.ok(!pysimProactiveSeqChanged(4));
|
||||||
|
assert.ok(pysimProactiveSeqChanged(5));
|
||||||
|
assert.ok(!pysimProactiveSeqChanged(undefined));
|
||||||
|
assert.ok(!pysimProactiveSeqChanged(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pysimStkStatusChanged detects menu state transitions', () => {
|
||||||
|
_pysimStkSig = null;
|
||||||
|
assert.ok(pysimStkStatusChanged({ active: false, pending: false }));
|
||||||
|
assert.ok(!pysimStkStatusChanged({ active: false, pending: false }));
|
||||||
|
assert.ok(pysimStkStatusChanged({ active: true, pending: true, pending_type: 'select_item' }));
|
||||||
|
assert.ok(!pysimStkStatusChanged({ active: true, pending: true, pending_type: 'select_item' }));
|
||||||
|
assert.ok(pysimStkStatusChanged({ active: true, pending: false }));
|
||||||
|
assert.ok(!pysimStkStatusChanged(null));
|
||||||
|
});
|
||||||
|
|||||||
@@ -1821,6 +1821,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
|||||||
'connected': connected,
|
'connected': connected,
|
||||||
'card_present': bool(getattr(self.server, 'card_present', False)),
|
'card_present': bool(getattr(self.server, 'card_present', False)),
|
||||||
'card_session': int(getattr(self.server, 'card_session', 0)),
|
'card_session': int(getattr(self.server, 'card_session', 0)),
|
||||||
|
'proactive_seq': _PROACTIVE_ENTRY_ID,
|
||||||
'equipping': bool(getattr(self.server, 'equipping', False)),
|
'equipping': bool(getattr(self.server, 'equipping', False)),
|
||||||
'auto_equip': bool(_AUTO_EQUIP),
|
'auto_equip': bool(_AUTO_EQUIP),
|
||||||
'card': card.name if card else None,
|
'card': card.name if card else None,
|
||||||
|
|||||||
Reference in New Issue
Block a user