ui: focus scan name input and start scanning on Enter

'Profile from card' and 'New snapshot' now focus the name field when the
dialog opens, and Enter in that field starts the scan
(profilerScanNameKeydown, ignored while the Scan button is disabled).
Tests for the key handler and the input wiring; SW cache v98 -> v99.
This commit is contained in:
2026-09-12 14:41:35 +03:00
parent 450f1de68a
commit a261675aad
4 changed files with 34 additions and 3 deletions
+10 -1
View File
@@ -956,7 +956,7 @@
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-sm w-full mx-4 max-h-[85vh] overflow-auto"> <div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-sm w-full mx-4 max-h-[85vh] overflow-auto">
<h3 id="profiler-scan-title" class="text-lg font-semibold mb-4 text-gray-800 dark:text-slate-200" data-l10n="Profile from card">Profile from card</h3> <h3 id="profiler-scan-title" class="text-lg font-semibold mb-4 text-gray-800 dark:text-slate-200" data-l10n="Profile from card">Profile from card</h3>
<label id="profiler-scan-name-label" class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Profile name">Profile name</label> <label id="profiler-scan-name-label" class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Profile name">Profile name</label>
<input id="profiler-scan-name" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-2 dark:bg-slate-800 mb-3" placeholder="Profile name"> <input id="profiler-scan-name" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-2 dark:bg-slate-800 mb-3" placeholder="Profile name" onkeydown="profilerScanNameKeydown(event)">
<div id="profiler-scan-options"> <div id="profiler-scan-options">
<div class="flex items-center gap-2 mb-1"> <div class="flex items-center gap-2 mb-1">
<span class="text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Ignore contents of files:">Ignore contents of files:</span> <span class="text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Ignore contents of files:">Ignore contents of files:</span>
@@ -7376,6 +7376,7 @@ function profilerFromCard() {
document.getElementById('profiler-scan-btn').disabled = false; document.getElementById('profiler-scan-btn').disabled = false;
document.getElementById('profiler-scan-btn').textContent = t('Scan'); document.getElementById('profiler-scan-btn').textContent = t('Scan');
document.getElementById('profiler-scan-modal').classList.remove('hidden'); document.getElementById('profiler-scan-modal').classList.remove('hidden');
document.getElementById('profiler-scan-name').focus();
} }
function profilerScanCancel() { function profilerScanCancel() {
@@ -7394,6 +7395,14 @@ function snapshotNew() {
document.getElementById('profiler-scan-btn').disabled = false; document.getElementById('profiler-scan-btn').disabled = false;
document.getElementById('profiler-scan-btn').textContent = t('Scan'); document.getElementById('profiler-scan-btn').textContent = t('Scan');
document.getElementById('profiler-scan-modal').classList.remove('hidden'); document.getElementById('profiler-scan-modal').classList.remove('hidden');
nameEl.focus();
}
function profilerScanNameKeydown(e) {
if (e.key !== 'Enter') return;
if (document.getElementById('profiler-scan-btn').disabled) return;
e.preventDefault();
profilerScanStart();
} }
async function profilerScanStart() { async function profilerScanStart() {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'otaman-v98'; const CACHE = 'otaman-v99';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+4
View File
@@ -33,3 +33,7 @@ test('phone simulator has Phone / TR Config pills', () => {
assert.ok(html.includes('id="phone-sub-phone"')); assert.ok(html.includes('id="phone-sub-phone"'));
assert.ok(html.includes('id="phone-sub-tr"')); assert.ok(html.includes('id="phone-sub-tr"'));
}); });
test('scan name input starts scanning on Enter', () => {
assert.match(html, /id="profiler-scan-name"[^>]*onkeydown="profilerScanNameKeydown\(event\)"/);
});
+19 -1
View File
@@ -21,7 +21,7 @@ function extractFunc(src, name, asyncFn) {
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1); return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
} }
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions', 'profilerLiveSource', 'profilerSnapshotSource', 'profilerVisibleResults', 'profilerMaskFidForFile', 'profilerRulesFromSnapshot', 'profilerExtraFileResults']; const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions', 'profilerLiveSource', 'profilerSnapshotSource', 'profilerVisibleResults', 'profilerMaskFidForFile', 'profilerRulesFromSnapshot', 'profilerExtraFileResults', 'profilerScanNameKeydown'];
let code = ''; let code = '';
for (const f of FNS) code += extractFunc(html, f) + '\n'; for (const f of FNS) code += extractFunc(html, f) + '\n';
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n'; code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
@@ -1112,3 +1112,21 @@ test('profilerExtraFileResults reports files missing from the master', () => {
assert.strictEqual(extras[0].status, 'fail'); assert.strictEqual(extras[0].status, 'fail');
assert.deepStrictEqual(extras[0].checks, [{ label: 'extra file', expected: 'absent', actual: 'present', ok: false }]); assert.deepStrictEqual(extras[0].checks, [{ label: 'extra file', expected: 'absent', actual: 'present', ok: false }]);
}); });
test('profilerScanNameKeydown starts the scan on Enter only', () => {
const btn = { disabled: false };
global.document = { getElementById: () => btn };
let started = 0;
global.profilerScanStart = () => { started++; };
let prevented = 0;
profilerScanNameKeydown({ key: 'Enter', preventDefault: () => prevented++ });
profilerScanNameKeydown({ key: 'a', preventDefault: () => prevented++ });
assert.strictEqual(started, 1);
assert.strictEqual(prevented, 1);
btn.disabled = true;
profilerScanNameKeydown({ key: 'Enter', preventDefault: () => prevented++ });
assert.strictEqual(started, 1);
assert.strictEqual(prevented, 1);
delete global.document;
delete global.profilerScanStart;
});