feat(web): Rotation, Operation History, Config Editor and Backup & Restore become ES-module pages (stage 2) (#727)

Rotation, Operation History, Config Editor and Backup & Restore become ES-module pages (stage 2): no inline scripts or onclick in the four partials, delegated data-action listeners, reads cancelled on swap-out, old globals kept as deprecated aliases through window.LEDMatrix, and four new DOM suites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-02 18:02:03 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent c14002edc3
commit a7b3f33952
22 changed files with 2019 additions and 819 deletions
+5
View File
@@ -4,6 +4,7 @@
* const api = createApi();
* const body = await api.get('/api/v3/cache/list', { signal });
* await api.post('/api/v3/cache/delete', { key }, { signal });
* await api.request('POST', '/api/v3/backup/validate', { body: formData });
*
* Every call resolves to the parsed JSON body, or rejects with an ApiError:
* error.status the HTTP status (0 when no HTTP answer arrived)
@@ -77,6 +78,10 @@ export function createApi(options = {}) {
if (opts.json !== undefined) {
init.headers['Content-Type'] = 'application/json';
init.body = JSON.stringify(opts.json);
} else if (opts.body !== undefined) {
// Sent as it is (a FormData upload, say); the browser sets the
// Content-Type, multipart boundary included.
init.body = opts.body;
}
let response;
+40 -4
View File
@@ -21,13 +21,49 @@ const registry = createRegistry({
});
const facade = installFacade(window, createFacade(window, api, registry));
/**
* A page's loader. It remembers the module once loaded, so an alias of a
* synchronous old global (validateJSON returns a boolean) can answer
* synchronously while its page is on screen.
*/
function page(load) {
let module = null;
const loader = function() {
return Promise.resolve(load()).then(function(loaded) { module = loaded; return loaded; });
};
loader.loaded = function() { return module; };
return loader;
}
// Converted pages. Each loads on first use: its module is fetched only when
// its partial (data-page="<name>") first appears.
registry.register('cache', function() { return import('../pages/cache.js'); });
const pages = {
'cache': page(function() { return import('../pages/cache.js'); }),
'durations': page(function() { return import('../pages/durations.js'); }),
'operation-history': page(function() { return import('../pages/operation-history.js'); }),
'raw-json': page(function() { return import('../pages/raw-json.js'); }),
'backup-restore': page(function() { return import('../pages/backup-restore.js'); }),
};
Object.keys(pages).forEach(function(name) { registry.register(name, pages[name]); });
/** Keep window[name] working: forward to the page module's export of the same name. */
function alias(pageName, name, replacement) {
const loader = pages[pageName];
facade.deprecate(name, function() {
const args = arguments;
const module = loader.loaded();
if (module) return module[name].apply(null, args);
return loader().then(function(loaded) { return loaded[name].apply(null, args); });
}, replacement);
}
// Old globals the converted pages used to define.
facade.deprecate('deleteCacheFile', function(key) {
return import('../pages/cache.js').then(function(page) { return page.deleteCacheFile(key); });
}, "the Cache tab's Delete buttons");
alias('cache', 'deleteCacheFile', "the Cache tab's Delete buttons");
['formatJson', 'manualValidateJson', 'validateJSON', 'saveMainConfig', 'saveSecretsConfig'].forEach(function(name) {
alias('raw-json', name, "the Config Editor tab's buttons");
});
['exportBackup', 'loadBackupList', 'validateRestoreFile', 'clearRestore', 'runRestore'].forEach(function(name) {
alias('backup-restore', name, "the Backup & Restore tab's buttons");
});
registry.start();