feat: next action suffix in the pending SELECT ITEM panel (v3.5.6)
The STK menu overlay's cached top menu showed the card-provided Items Next Action Indicator (TS 102 223 8.24) as a gray suffix, but the pending SELECT ITEM list the card returns mid-dialogue did not: the server already attaches nai_name (via _parse_select_item), the panel just had its own row markup without the suffix. Both lists now render through stkMenuItemsHtml(items, handler) - identical rows (id, text, optional gray suffix) with the per-list click callback (stkMenuItemClick / stkSubItemClick). stk_menu.test.js: the shared renderer (suffix only with nai_name, handler in the row, null list) plus a call-site guard for both lists. 554 frontend / 421 Python green; version 3.5.6; sw cache simple-v259.
This commit is contained in:
+17
-17
@@ -1559,7 +1559,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.5.5';
|
||||
const SIMPLE_VERSION = '3.5.6';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -7710,19 +7710,26 @@ function stkMenuNaiSuffix(item) {
|
||||
return (item && item.nai_name) ? '\u25b8 ' + item.nai_name : '';
|
||||
}
|
||||
|
||||
// The overlay's item list, shared by the cached top menu and the pending
|
||||
// SELECT ITEM items the card returns mid-dialogue: one row per item, each
|
||||
// with the card-provided next action suffix. `handler` is the click callback
|
||||
// (stkMenuItemClick / stkSubItemClick).
|
||||
function stkMenuItemsHtml(items, handler) {
|
||||
let html = '<div class="space-y-1">';
|
||||
(items || []).forEach(item => {
|
||||
const nai = stkMenuNaiSuffix(item);
|
||||
html += '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer" onclick="' + handler + '(' + item.id + ')"><span class="w-5 text-xs text-gray-300 dark:text-gray-600 text-right">' + item.id + '</span><span class="text-sm">' + esc(item.text) + '</span>' +
|
||||
(nai ? '<span class="text-xs text-gray-400 dark:text-slate-500">' + esc(nai) + '</span>' : '') + '</div>';
|
||||
});
|
||||
return html + '</div>';
|
||||
}
|
||||
|
||||
function stkMenuRenderItems() {
|
||||
const content = document.getElementById('stk-menu-panel-content');
|
||||
content.innerHTML = '<span class="text-gray-500 text-sm">Loading...</span>';
|
||||
pysimFetch('/api/menu').then(data => {
|
||||
if (!data || !data.items) { content.innerHTML = '<span class="text-red-500 text-sm">No menu data</span>'; return; }
|
||||
let html = '<div class="space-y-1">';
|
||||
data.items.forEach(item => {
|
||||
const nai = stkMenuNaiSuffix(item);
|
||||
html += '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer" onclick="stkMenuItemClick(' + item.id + ')"><span class="w-5 text-xs text-gray-300 dark:text-gray-600 text-right">' + item.id + '</span><span class="text-sm">' + esc(item.text) + '</span>' +
|
||||
(nai ? '<span class="text-xs text-gray-400 dark:text-slate-500">' + esc(nai) + '</span>' : '') + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
content.innerHTML = html;
|
||||
content.innerHTML = stkMenuItemsHtml(data.items, 'stkMenuItemClick');
|
||||
}).catch(() => { content.innerHTML = '<span class="text-red-500 text-sm">Error loading menu</span>'; });
|
||||
}
|
||||
|
||||
@@ -7751,14 +7758,7 @@ function stkMenuHandleResponse(data) {
|
||||
btns.classList.remove('hidden');
|
||||
} else if (data.type === 'select_item') {
|
||||
stkMenuStack.push(data.items);
|
||||
content.innerHTML = '';
|
||||
const items = data.items;
|
||||
let html = '<div class="space-y-1">';
|
||||
items.forEach(item => {
|
||||
html += '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer" onclick="stkSubItemClick(' + item.id + ')"><span class="w-5 text-xs text-gray-300 dark:text-gray-600 text-right">' + item.id + '</span><span class="text-sm">' + esc(item.text) + '</span></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
content.innerHTML = html;
|
||||
content.innerHTML = stkMenuItemsHtml(data.items, 'stkSubItemClick');
|
||||
backBtn.style.display = '';
|
||||
document.getElementById('stk-ok-btn').style.display = 'none';
|
||||
btns.classList.remove('hidden');
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v258';
|
||||
const CACHE = 'simple-v259';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -23,6 +23,7 @@ function extractFunc(src, name, asyncFn) {
|
||||
|
||||
let code = extractFunc(html, 'stkMenuRespond', true) + '\n';
|
||||
code += extractFunc(html, 'stkMenuNaiSuffix') + '\n';
|
||||
code += extractFunc(html, 'stkMenuItemsHtml') + '\n';
|
||||
code += 'globalThis.esc = s => s;\n';
|
||||
eval(code);
|
||||
|
||||
@@ -47,6 +48,27 @@ test('the STK menu item suffix shows the item next action (8.24)', () => {
|
||||
assert.strictEqual(stkMenuNaiSuffix(null), '');
|
||||
});
|
||||
|
||||
test('the overlay item list shows the next action and the row handler', () => {
|
||||
const list = stkMenuItemsHtml([
|
||||
{ id: 1, text: 'Menu', nai: 0x25, nai_name: 'SET UP MENU' },
|
||||
{ id: 2, text: 'Info' },
|
||||
], 'stkSubItemClick');
|
||||
assert.match(list, /onclick="stkSubItemClick\(1\)"/);
|
||||
assert.match(list, /onclick="stkSubItemClick\(2\)"/);
|
||||
assert.ok(list.includes('Menu'));
|
||||
assert.ok(list.includes('\u25b8 SET UP MENU'));
|
||||
// the second item has no indicator -> exactly one suffix in the list
|
||||
assert.strictEqual((list.match(/\u25b8/g) || []).length, 1);
|
||||
assert.strictEqual(stkMenuItemsHtml(null, 'stkMenuItemClick'), '<div class="space-y-1"></div>');
|
||||
});
|
||||
|
||||
test('both overlay lists render through the shared row helper', () => {
|
||||
// the cached top menu and the pending SELECT ITEM items must use the same
|
||||
// renderer, otherwise the latter silently loses the NAI suffix
|
||||
assert.match(html, /stkMenuItemsHtml\(data\.items, 'stkMenuItemClick'\)/);
|
||||
assert.match(html, /stkMenuItemsHtml\(data\.items, 'stkSubItemClick'\)/);
|
||||
});
|
||||
|
||||
test('back with a fetched SELECT ITEM continues the card dialogue', async () => {
|
||||
const data = { type: 'select_item', items: [{ id: 1, text: 'Info' }] };
|
||||
const calls = setup(data);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.5.5"
|
||||
version = "3.5.6"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -29,7 +29,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.5.5'
|
||||
VERSION = '3.5.6'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user