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 =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// 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;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
@@ -7710,19 +7710,26 @@ function stkMenuNaiSuffix(item) {
|
|||||||
return (item && item.nai_name) ? '\u25b8 ' + item.nai_name : '';
|
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() {
|
function stkMenuRenderItems() {
|
||||||
const content = document.getElementById('stk-menu-panel-content');
|
const content = document.getElementById('stk-menu-panel-content');
|
||||||
content.innerHTML = '<span class="text-gray-500 text-sm">Loading...</span>';
|
content.innerHTML = '<span class="text-gray-500 text-sm">Loading...</span>';
|
||||||
pysimFetch('/api/menu').then(data => {
|
pysimFetch('/api/menu').then(data => {
|
||||||
if (!data || !data.items) { content.innerHTML = '<span class="text-red-500 text-sm">No menu data</span>'; return; }
|
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">';
|
content.innerHTML = stkMenuItemsHtml(data.items, 'stkMenuItemClick');
|
||||||
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;
|
|
||||||
}).catch(() => { content.innerHTML = '<span class="text-red-500 text-sm">Error loading menu</span>'; });
|
}).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');
|
btns.classList.remove('hidden');
|
||||||
} else if (data.type === 'select_item') {
|
} else if (data.type === 'select_item') {
|
||||||
stkMenuStack.push(data.items);
|
stkMenuStack.push(data.items);
|
||||||
content.innerHTML = '';
|
content.innerHTML = stkMenuItemsHtml(data.items, 'stkSubItemClick');
|
||||||
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;
|
|
||||||
backBtn.style.display = '';
|
backBtn.style.display = '';
|
||||||
document.getElementById('stk-ok-btn').style.display = 'none';
|
document.getElementById('stk-ok-btn').style.display = 'none';
|
||||||
btns.classList.remove('hidden');
|
btns.classList.remove('hidden');
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v258';
|
const CACHE = 'simple-v259';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function extractFunc(src, name, asyncFn) {
|
|||||||
|
|
||||||
let code = extractFunc(html, 'stkMenuRespond', true) + '\n';
|
let code = extractFunc(html, 'stkMenuRespond', true) + '\n';
|
||||||
code += extractFunc(html, 'stkMenuNaiSuffix') + '\n';
|
code += extractFunc(html, 'stkMenuNaiSuffix') + '\n';
|
||||||
|
code += extractFunc(html, 'stkMenuItemsHtml') + '\n';
|
||||||
code += 'globalThis.esc = s => s;\n';
|
code += 'globalThis.esc = s => s;\n';
|
||||||
eval(code);
|
eval(code);
|
||||||
|
|
||||||
@@ -47,6 +48,27 @@ test('the STK menu item suffix shows the item next action (8.24)', () => {
|
|||||||
assert.strictEqual(stkMenuNaiSuffix(null), '');
|
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 () => {
|
test('back with a fetched SELECT ITEM continues the card dialogue', async () => {
|
||||||
const data = { type: 'select_item', items: [{ id: 1, text: 'Info' }] };
|
const data = { type: 'select_item', items: [{ id: 1, text: 'Info' }] };
|
||||||
const calls = setup(data);
|
const calls = setup(data);
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.5.5"
|
version = "3.5.6"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# 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
|
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
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user