ui: file manager read pills and Edit raw (v2.7.9)
The decoded view is a read-only field table, so pressing Edit there did nothing (no readonly inputs to strip) and Save found nothing to write. - Remove the redundant Read button: the Raw/Decoded pills already read the file, so they become explicit read actions — Read raw / Read decoded (Читать сырые данные / Декодировать). They are buttons now so the server/card availability model disables them like the old Read button did. - Edit raw (Редактировать сырые данные) always edits the hex view: from decoded (or with no content loaded yet) it switches back to raw and reads the file first; the decoded view stays read-only. - Save/Cancel moved next to Edit raw (right-aligned group); the pills are inert and dimmed while editing (mode switching would discard unsaved changes). - Tests: frontend/tests/fs_edit.test.js (skipRead, edit guard, decoded auto-switch ordering, read failure abort, cancel/reset); help EN/RU.
This commit is contained in:
@@ -352,9 +352,8 @@
|
||||
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 Файловый менеджер</h3>
|
||||
<p class="text-sm mb-2">Дерево файловой системы отображается слева; выбор файла открывает панель деталей справа. Элементы сгруппированы: DF выше EF, сортировка по <strong>FID</strong> или символьному <strong>имени</strong> (пиллы и кнопка <strong>«Проверить все файлы»</strong> закреплены над прокручиваемым деревом; выбор сохраняется в <code class="font-mono text-sm">localStorage</code>). При выборе файла над содержимым также показываются FID, тип файла, размер / структура записей и декодированный FCI.</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Прочитать</strong> — чтение файла (автоопределение transparent/record)</li>
|
||||
<li><strong>Редактировать</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>)</li>
|
||||
<li><strong>Данные как на карте / Декодированные данные</strong> — переключение между hex-дампом и таблицей декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); серверный pySim JSON того же чтения остаётся доступен в свёрнутом блоке <em>pySim JSON (сервер)</em></li>
|
||||
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); серверный pySim JSON того же чтения остаётся доступен в свёрнутом блоке <em>pySim JSON (сервер)</em>. Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл.</li>
|
||||
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. Декодированный вид доступен только для чтения — «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
||||
<li><strong>Проверить все файлы</strong> — обход всего дерева (включая пользовательские файлы) с пометкой каждого элемента: есть (обычный вид) или нет (красный ✗, без стрелки разворачивания); существующие пустые DF показывают <code class="font-mono text-sm">(пусто)</code>. Отображается прогресс <em>N / всего</em>, обход можно остановить; в конце — сводка «есть/нет». Файлы проверяются только при разворачивании или проверке — просмотр остаётся ленивым.</li>
|
||||
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящей предустановке карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
||||
</ul>
|
||||
|
||||
+2
-3
@@ -352,9 +352,8 @@
|
||||
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 File manager</h3>
|
||||
<p class="text-sm mb-2">The file system tree is displayed on the left; selecting a file opens its detail pane on the right. Entries are grouped with DFs above EFs and sorted by <strong>FID</strong> or symbolic <strong>Name</strong> (pills pinned above the scrolling tree together with <strong>Probe all files</strong>; the choice is remembered in <code class="font-mono text-sm">localStorage</code>). Selecting a file also shows its FID, file type, size / record layout and the decoded FCI above the content pane.</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Read</strong> — reads the selected file (auto-detects transparent vs record files)</li>
|
||||
<li><strong>Edit</strong> — modify hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>)</li>
|
||||
<li><strong>Raw / Decoded</strong> — toggle between the hex dump and a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the server-side pySim JSON of the same read stays available in the collapsed <em>pySim JSON (server)</em> disclosure</li>
|
||||
<li><strong>Read raw / Read decoded</strong> — read the selected file as a hex dump or as a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the server-side pySim JSON of the same read stays available in the collapsed <em>pySim JSON (server)</em> disclosure. The highlighted pill is the current view; clicking either pill (re-)reads the file.</li>
|
||||
<li><strong>Edit raw</strong> — edit the raw hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>); transparent files use a textarea, record files one input per record with selection checkboxes. The decoded view is read-only — <strong>Edit raw</strong> switches to the hex view first and reads the file if needed.</li>
|
||||
<li><strong>Probe all files</strong> — walks the whole tree (including custom files) and marks every entry present (normal) or absent (red ✗, no expand arrow); empty-but-present DFs show <code class="font-mono text-sm">(empty)</code>. Shows progress <em>N / total</em>, can be stopped, and finishes with a present/absent summary. Files are only verified when expanded or probed — browsing stays lazy.</li>
|
||||
<li><strong>ADM</strong> — files that need the administrator PIN fail with <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; if the matching card preset (same ICCID) carries an ADM key, a <strong>Verify ADM</strong> button appears next to the error and the header badge (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) becomes clickable. Every wrong key consumes an attempt: the remaining attempts are shown and a retry asks for confirmation. A blocked ADM cannot be recovered here — it needs the card’s unblock key.</li>
|
||||
</ul>
|
||||
|
||||
+43
-18
@@ -861,13 +861,13 @@
|
||||
<div id="pysim-fs-info" class="mb-1"></div>
|
||||
<div id="pysim-fs-content" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1.5 bg-gray-100 dark:bg-slate-800 min-h-20 overflow-y-auto"></div>
|
||||
<div class="flex gap-2 mt-1 items-center">
|
||||
<button data-needs="card" onclick="pysimFsRead()" id="pysim-fs-read-btn" class="px-2 py-1 text-xs rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Read">Read</button>
|
||||
<button type="button" data-needs="card" onclick="pysimFsSetMode('raw')" id="pysim-fs-read-raw-btn" class="pysim-mode-pill px-2 py-0.5 text-xs rounded cursor-pointer bg-blue-600 text-white disabled:opacity-40 disabled:cursor-not-allowed" data-mode="raw" data-l10n="Read raw">Read raw</button>
|
||||
<button type="button" data-needs="card" onclick="pysimFsSetMode('dec')" id="pysim-fs-read-dec-btn" class="pysim-mode-pill px-2 py-0.5 text-xs rounded cursor-pointer bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 disabled:opacity-40 disabled:cursor-not-allowed" data-mode="dec" data-l10n="Read decoded">Read decoded</button>
|
||||
<div class="ml-auto flex gap-2">
|
||||
<button data-needs="card" onclick="pysimFsSave()" id="pysim-fs-save-btn" class="px-2 py-1 text-xs rounded bg-emerald-600 text-white hover:bg-emerald-700 hidden disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Save">Save</button>
|
||||
<button onclick="pysimFsCancel()" id="pysim-fs-cancel-btn" class="px-2 py-1 text-xs rounded bg-gray-600 text-white hover:bg-gray-700 hidden" data-l10n="Cancel">Cancel</button>
|
||||
<span class="text-xs text-gray-400 dark:text-slate-500">|</span>
|
||||
<span class="pysim-mode-pill px-2 py-0.5 text-xs rounded cursor-pointer bg-blue-600 text-white" data-mode="raw" onclick="pysimFsSetMode('raw')" data-l10n="Raw">Raw</span>
|
||||
<span class="pysim-mode-pill px-2 py-0.5 text-xs rounded cursor-pointer bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300" data-mode="dec" onclick="pysimFsSetMode('dec')" data-l10n="Decoded">Decoded</span>
|
||||
<button data-needs="card" onclick="pysimFsEdit()" id="pysim-fs-edit-btn" class="px-2 py-1 text-xs rounded bg-blue-600 text-white hover:bg-blue-700 ml-auto disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Edit">Edit</button>
|
||||
<button data-needs="card" onclick="pysimFsEdit()" id="pysim-fs-edit-btn" class="px-2 py-1 text-xs rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Edit raw">Edit raw</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pysim-fs-status" class="mt-1 text-xs font-mono text-gray-500 dark:text-slate-400" style="min-height:1.2em"></div>
|
||||
</div>
|
||||
@@ -1413,7 +1413,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 = '2.7.8';
|
||||
const SIMPLE_VERSION = '2.7.9';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -7176,6 +7176,7 @@ async function pysimFsClickFile(name) {
|
||||
}
|
||||
|
||||
async function pysimFsRead() {
|
||||
if (!pysimFsSelected) return false;
|
||||
const rawName = pysimFsSelected;
|
||||
const out = document.getElementById('pysim-fs-content');
|
||||
const statusEl = document.getElementById('pysim-fs-status');
|
||||
@@ -7188,7 +7189,7 @@ async function pysimFsRead() {
|
||||
const data = await pysimFetch('/api/read', body);
|
||||
if (!data.success) {
|
||||
pysimFsShowError(statusEl, data.sw, data.error);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
statusEl.textContent = 'SW: ' + data.sw + ' OK';
|
||||
if (pysimFsDecodedMode) {
|
||||
@@ -7206,8 +7207,10 @@ async function pysimFsRead() {
|
||||
} else {
|
||||
out.innerHTML = '<textarea rows="8" readonly class="w-full font-mono text-xs border-0 bg-transparent resize-none">' + esc(data.data || '(no output)') + '</textarea>';
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Error: ' + e.message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7267,13 +7270,22 @@ async function pysimFsLoadPySimJson() {
|
||||
let pysimFsEditMode = false;
|
||||
let pysimFsEditData = null;
|
||||
|
||||
function pysimFsEdit() {
|
||||
// Edit raw: transparent files become an editable textarea, record files
|
||||
// per-record inputs with selection checkboxes. The decoded view is a
|
||||
// read-only field table and a freshly selected file has no content yet, so
|
||||
// make sure the editable raw view is loaded first (the pills are read
|
||||
// actions; Edit switches back to raw and reads on demand).
|
||||
async function pysimFsEdit() {
|
||||
const out = document.getElementById('pysim-fs-content');
|
||||
if (pysimFsDecodedMode || !out.innerHTML.trim()) {
|
||||
pysimFsSetMode('raw', true);
|
||||
if (!await pysimFsRead()) return;
|
||||
}
|
||||
pysimFsEditMode = true;
|
||||
document.getElementById('pysim-fs-read-btn').classList.add('hidden');
|
||||
document.getElementById('pysim-fs-edit-btn').classList.add('hidden');
|
||||
document.getElementById('pysim-fs-save-btn').classList.remove('hidden');
|
||||
document.getElementById('pysim-fs-cancel-btn').classList.remove('hidden');
|
||||
const out = document.getElementById('pysim-fs-content');
|
||||
pysimFsPillsEnabled(false);
|
||||
out.querySelectorAll('textarea, input:not([type=checkbox])').forEach(el => el.removeAttribute('readonly'));
|
||||
out.querySelectorAll('.pysim-fs-record-cb').forEach(cb => cb.classList.remove('hidden'));
|
||||
out.querySelectorAll('input:not([type=checkbox])').forEach(el => {
|
||||
@@ -7287,10 +7299,10 @@ function pysimFsEdit() {
|
||||
|
||||
function pysimFsCancel() {
|
||||
pysimFsEditMode = false;
|
||||
document.getElementById('pysim-fs-read-btn').classList.remove('hidden');
|
||||
document.getElementById('pysim-fs-edit-btn').classList.remove('hidden');
|
||||
document.getElementById('pysim-fs-save-btn').classList.add('hidden');
|
||||
document.getElementById('pysim-fs-cancel-btn').classList.add('hidden');
|
||||
pysimFsPillsEnabled(true);
|
||||
const out = document.getElementById('pysim-fs-content');
|
||||
if (pysimFsEditData) out.innerHTML = pysimFsEditData;
|
||||
pysimFsEditData = null;
|
||||
@@ -7350,7 +7362,12 @@ async function pysimFsSave() {
|
||||
}
|
||||
}
|
||||
|
||||
function pysimFsSetMode(mode) {
|
||||
// The Read raw / Read decoded pills are the read actions: they switch the
|
||||
// view mode and (re-)read the selected file. `skipRead` updates only the
|
||||
// pills — used by pysimFsEdit() right before its own read. While editing
|
||||
// they are inert: re-rendering would discard unsaved changes.
|
||||
function pysimFsSetMode(mode, skipRead) {
|
||||
if (pysimFsEditMode) return;
|
||||
pysimFsDecodedMode = mode === 'dec';
|
||||
document.querySelectorAll('.pysim-mode-pill').forEach(el => {
|
||||
const active = el.dataset.mode === mode;
|
||||
@@ -7361,18 +7378,25 @@ function pysimFsSetMode(mode) {
|
||||
el.classList.toggle('text-gray-700', !active);
|
||||
el.classList.toggle('dark:text-slate-300', !active);
|
||||
});
|
||||
// Re-render the open file in the new mode (not while editing: that would
|
||||
// discard unsaved changes).
|
||||
if (pysimFsSelected && !pysimFsEditMode) pysimFsRead();
|
||||
if (!skipRead && pysimFsSelected) pysimFsRead();
|
||||
}
|
||||
|
||||
// Visual cue for the inert pills while editing (pysimFsSetMode guards the
|
||||
// click; this only dims them).
|
||||
function pysimFsPillsEnabled(enabled) {
|
||||
document.querySelectorAll('.pysim-mode-pill').forEach(el => {
|
||||
el.classList.toggle('opacity-50', !enabled);
|
||||
el.classList.toggle('pointer-events-none', !enabled);
|
||||
});
|
||||
}
|
||||
|
||||
function pysimFsResetEdit() {
|
||||
pysimFsEditMode = false;
|
||||
pysimFsEditData = null;
|
||||
document.getElementById('pysim-fs-read-btn').classList.remove('hidden');
|
||||
document.getElementById('pysim-fs-edit-btn').classList.remove('hidden');
|
||||
document.getElementById('pysim-fs-save-btn').classList.add('hidden');
|
||||
document.getElementById('pysim-fs-cancel-btn').classList.add('hidden');
|
||||
pysimFsPillsEnabled(true);
|
||||
}
|
||||
|
||||
// ===== proactive UICC events =====
|
||||
@@ -13055,11 +13079,12 @@ const LANG_RU = {
|
||||
'Custom files': 'Пользовательские файлы',
|
||||
'pySim command line': 'Командная строка pySim',
|
||||
'Raw APDU': 'Отправка APDU',
|
||||
'Read': 'Прочитать',
|
||||
'Read raw': 'Читать сырые данные',
|
||||
'Read decoded': 'Декодировать',
|
||||
'Edit raw': 'Редактировать сырые данные',
|
||||
'Refresh': 'Обновить',
|
||||
'Last refresh': 'Последнее обновление',
|
||||
'Save': 'Сохранить',
|
||||
'Raw': 'Данные как на карте',
|
||||
'Decoded': 'Декодированные данные',
|
||||
'Edit': 'Редактировать',
|
||||
'Clone': 'Клонировать',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v211';
|
||||
const CACHE = 'simple-v212';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
|
||||
function extractFunc(src, name) {
|
||||
const re = new RegExp('(?:async\\s+)?function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const m = re.exec(src);
|
||||
if (!m) throw new Error('function ' + name + ' not found');
|
||||
let i = m.index + m[0].length - 1;
|
||||
let depth = 0;
|
||||
for (; i < src.length; i++) {
|
||||
if (src[i] === '{') depth++;
|
||||
else if (src[i] === '}') {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
let code = 'var pysimFsDecodedMode = false;\n'
|
||||
+ 'var pysimFsEditMode = false;\n'
|
||||
+ 'var pysimFsEditData = null;\n'
|
||||
+ 'var pysimFsSelected = null;\n';
|
||||
for (const fn of ['pysimFsSetMode', 'pysimFsPillsEnabled', 'pysimFsEdit',
|
||||
'pysimFsCancel', 'pysimFsResetEdit']) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
eval(code);
|
||||
|
||||
let events = [];
|
||||
let readCalls = 0;
|
||||
let readResult = true;
|
||||
let content, buttons, pills, editables, checkboxes, inputs;
|
||||
|
||||
function fakeClassList() {
|
||||
const set = new Set();
|
||||
return {
|
||||
add: (...c) => c.forEach(x => set.add(x)),
|
||||
remove: (...c) => c.forEach(x => set.delete(x)),
|
||||
toggle: (c, on) => { if (on) set.add(c); else set.delete(c); },
|
||||
contains: c => set.has(c),
|
||||
};
|
||||
}
|
||||
|
||||
function fakeEl() {
|
||||
return { classList: fakeClassList(), dataset: {} };
|
||||
}
|
||||
|
||||
function setup(opts) {
|
||||
opts = opts || {};
|
||||
events = [];
|
||||
readCalls = 0;
|
||||
readResult = opts.readResult !== false;
|
||||
editables = [{ removeAttribute: () => events.push('readonly-removed') }];
|
||||
checkboxes = [fakeEl()];
|
||||
inputs = [{ addEventListener: () => {} }];
|
||||
content = fakeEl();
|
||||
content.innerHTML = opts.contentHtml || '';
|
||||
content.querySelectorAll = sel => {
|
||||
if (sel === '.pysim-fs-record-cb') return checkboxes;
|
||||
if (sel.indexOf('textarea') === 0) return editables;
|
||||
return inputs;
|
||||
};
|
||||
buttons = {
|
||||
'pysim-fs-edit-btn': fakeEl(),
|
||||
'pysim-fs-save-btn': fakeEl(),
|
||||
'pysim-fs-cancel-btn': fakeEl(),
|
||||
};
|
||||
buttons['pysim-fs-save-btn'].classList.add('hidden');
|
||||
buttons['pysim-fs-cancel-btn'].classList.add('hidden');
|
||||
pills = [fakeEl(), fakeEl()];
|
||||
pills[0].dataset.mode = 'raw';
|
||||
pills[1].dataset.mode = 'dec';
|
||||
|
||||
pysimFsDecodedMode = !!opts.decoded;
|
||||
pysimFsEditMode = false;
|
||||
pysimFsEditData = null;
|
||||
pysimFsSelected = 'EF.IMSI';
|
||||
|
||||
globalThis.document = {
|
||||
getElementById: id => id === 'pysim-fs-content' ? content : (buttons[id] || null),
|
||||
querySelectorAll: sel => sel === '.pysim-mode-pill' ? pills : [],
|
||||
};
|
||||
globalThis.pysimFsRead = async () => {
|
||||
readCalls++;
|
||||
events.push('read');
|
||||
return readResult;
|
||||
};
|
||||
return {
|
||||
content,
|
||||
btn: id => buttons['pysim-fs-' + id + '-btn'],
|
||||
pill: mode => pills.find(p => p.dataset.mode === mode),
|
||||
reads: () => readCalls,
|
||||
readonlyRemoved: () => events.filter(e => e === 'readonly-removed').length,
|
||||
events,
|
||||
};
|
||||
}
|
||||
|
||||
test('Read raw / Read decoded pills switch the view and read the file', () => {
|
||||
const h = setup();
|
||||
pysimFsSetMode('raw', true);
|
||||
assert.strictEqual(pysimFsDecodedMode, false);
|
||||
assert.strictEqual(h.reads(), 0, 'skipRead updates the pills only');
|
||||
assert.ok(h.pill('raw').classList.contains('bg-blue-600'));
|
||||
assert.ok(!h.pill('dec').classList.contains('bg-blue-600'));
|
||||
|
||||
pysimFsSetMode('dec');
|
||||
assert.strictEqual(pysimFsDecodedMode, true);
|
||||
assert.strictEqual(h.reads(), 1, 'a pill click reads the selected file');
|
||||
assert.ok(h.pill('dec').classList.contains('bg-blue-600'));
|
||||
assert.ok(!h.pill('raw').classList.contains('bg-blue-600'));
|
||||
});
|
||||
|
||||
test('pill clicks are ignored while editing', () => {
|
||||
const h = setup();
|
||||
pysimFsDecodedMode = true;
|
||||
pysimFsEditMode = true;
|
||||
pysimFsSetMode('raw');
|
||||
assert.strictEqual(pysimFsDecodedMode, true, 'the view mode must not change mid-edit');
|
||||
assert.strictEqual(h.reads(), 0);
|
||||
});
|
||||
|
||||
test('Edit raw keeps a loaded raw view and enters edit mode without re-reading', async () => {
|
||||
const h = setup({ contentHtml: '<textarea readonly>aa</textarea>' });
|
||||
await pysimFsEdit();
|
||||
assert.strictEqual(h.reads(), 0);
|
||||
assert.strictEqual(pysimFsEditMode, true);
|
||||
assert.ok(h.btn('edit').classList.contains('hidden'));
|
||||
assert.ok(!h.btn('save').classList.contains('hidden'));
|
||||
assert.ok(!h.btn('cancel').classList.contains('hidden'));
|
||||
assert.strictEqual(h.readonlyRemoved(), 1);
|
||||
assert.ok(h.pill('raw').classList.contains('opacity-50'), 'pills are dimmed');
|
||||
assert.ok(h.pill('raw').classList.contains('pointer-events-none'));
|
||||
});
|
||||
|
||||
test('Edit raw switches from the decoded view to raw and reads before editing', async () => {
|
||||
const h = setup({ decoded: true });
|
||||
await pysimFsEdit();
|
||||
assert.strictEqual(pysimFsDecodedMode, false);
|
||||
assert.strictEqual(h.reads(), 1);
|
||||
assert.deepStrictEqual(h.events, ['read', 'readonly-removed'],
|
||||
'the raw view must be rendered before the readonly attributes are stripped');
|
||||
assert.ok(h.pill('raw').classList.contains('bg-blue-600'));
|
||||
assert.strictEqual(pysimFsEditMode, true);
|
||||
});
|
||||
|
||||
test('Edit raw reads a fresh selection and aborts if the read fails', async () => {
|
||||
const h = setup({ readResult: false });
|
||||
await pysimFsEdit();
|
||||
assert.strictEqual(h.reads(), 1);
|
||||
assert.strictEqual(pysimFsEditMode, false);
|
||||
assert.ok(h.btn('save').classList.contains('hidden'));
|
||||
assert.ok(!h.pill('raw').classList.contains('opacity-50'));
|
||||
});
|
||||
|
||||
test('Cancel restores the view and re-enables the pills', async () => {
|
||||
const h = setup({ contentHtml: '<textarea readonly>aa</textarea>' });
|
||||
await pysimFsEdit();
|
||||
pysimFsCancel();
|
||||
assert.strictEqual(pysimFsEditMode, false);
|
||||
assert.strictEqual(h.content.innerHTML, '<textarea readonly>aa</textarea>');
|
||||
assert.ok(!h.pill('raw').classList.contains('opacity-50'));
|
||||
assert.ok(!h.pill('raw').classList.contains('pointer-events-none'));
|
||||
assert.ok(!h.btn('edit').classList.contains('hidden'));
|
||||
assert.ok(h.btn('save').classList.contains('hidden'));
|
||||
assert.ok(h.btn('cancel').classList.contains('hidden'));
|
||||
});
|
||||
|
||||
test('Reset edit re-enables the pills', () => {
|
||||
const h = setup();
|
||||
pysimFsEditMode = true;
|
||||
pysimFsPillsEnabled(false);
|
||||
pysimFsResetEdit();
|
||||
assert.strictEqual(pysimFsEditMode, false);
|
||||
assert.ok(!h.pill('dec').classList.contains('opacity-50'));
|
||||
});
|
||||
|
||||
test('the file manager row exposes the read pills and Edit raw', () => {
|
||||
assert.ok(!html.includes('id="pysim-fs-read-btn"'), 'the redundant Read button is gone');
|
||||
assert.ok(html.includes('data-l10n="Read raw">Read raw<'));
|
||||
assert.ok(html.includes('data-l10n="Read decoded">Read decoded<'));
|
||||
assert.ok(html.includes('data-l10n="Edit raw">Edit raw<'));
|
||||
assert.ok(html.includes('id="pysim-fs-read-raw-btn"') && html.includes('data-needs="card"'));
|
||||
});
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "2.7.8"
|
||||
version = "2.7.9"
|
||||
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.
|
||||
|
||||
@@ -26,7 +26,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '2.7.8'
|
||||
VERSION = '2.7.9'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user