mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 13:52:10 +00:00
170 lines
6.9 KiB
JavaScript
170 lines
6.9 KiB
JavaScript
(function (global) {
|
|
"use strict";
|
|
|
|
function validateCatalog(catalog) {
|
|
if (!catalog || catalog.schema !== 1 || !Array.isArray(catalog.repositories) ||
|
|
!Array.isArray(catalog.releases) || !Array.isArray(catalog.builds) ||
|
|
!Array.isArray(catalog.unresolved) || !catalog.layouts ||
|
|
typeof catalog.layouts !== "object" || Array.isArray(catalog.layouts)) {
|
|
throw new Error("Unsupported partition catalog format");
|
|
}
|
|
return catalog;
|
|
}
|
|
|
|
function snapshot(catalog) {
|
|
return {
|
|
schema: catalog.schema,
|
|
generatedAt: catalog.generatedAt,
|
|
repositories: catalog.repositories,
|
|
counts: {
|
|
releases: catalog.releases.length,
|
|
builds: catalog.builds.length,
|
|
layouts: Object.keys(catalog.layouts).length,
|
|
unresolved: catalog.unresolved.length,
|
|
},
|
|
};
|
|
}
|
|
|
|
function jsonView(catalog, view) {
|
|
validateCatalog(catalog);
|
|
if (view === "all") return JSON.stringify(catalog, null, 2);
|
|
const result = snapshot(catalog);
|
|
if (view === "layouts") result.layouts = catalog.layouts;
|
|
else if (view === "releases") result.releases = catalog.releases;
|
|
else throw new Error("Unknown catalog view");
|
|
return JSON.stringify(result, null, 2);
|
|
}
|
|
|
|
function boardMinimums(catalog) {
|
|
validateCatalog(catalog);
|
|
const boards = new Map();
|
|
catalog.builds.forEach(function (build) {
|
|
const key = build.board.toLowerCase();
|
|
if (!boards.has(key)) {
|
|
boards.set(key, {
|
|
board: build.board, smallestBytes: null,
|
|
hasDualOta: false, hasNonDualOta: false, hasUnknownLayout: false,
|
|
});
|
|
}
|
|
const row = boards.get(key);
|
|
const layout = Object.prototype.hasOwnProperty.call(catalog.layouts, build.layout)
|
|
? catalog.layouts[build.layout] : null;
|
|
if (!layout || !Array.isArray(layout.partitions)) {
|
|
row.hasUnknownLayout = true;
|
|
return;
|
|
}
|
|
const apps = layout.partitions.filter(function (entry) {
|
|
return Array.isArray(entry) && entry[0] === 0 &&
|
|
Number.isSafeInteger(entry[3]) && entry[3] > 0;
|
|
});
|
|
if (!apps.length) {
|
|
row.hasUnknownLayout = true;
|
|
return;
|
|
}
|
|
const smallest = Math.min.apply(null, apps.map((entry) => entry[3]));
|
|
row.smallestBytes = row.smallestBytes === null
|
|
? smallest : Math.min(row.smallestBytes, smallest);
|
|
if (layout.dualOta === true) row.hasDualOta = true;
|
|
else if (layout.dualOta === false) row.hasNonDualOta = true;
|
|
else row.hasUnknownLayout = true;
|
|
});
|
|
return Array.from(boards.values()).sort(function (left, right) {
|
|
return left.board.localeCompare(right.board, "en", { sensitivity: "base" });
|
|
});
|
|
}
|
|
|
|
function formatSize(bytes) {
|
|
return bytes === null ? "Unknown" : (bytes / 1048576).toLocaleString(
|
|
"en-US", { maximumFractionDigits: 4 }
|
|
) + " MiB";
|
|
}
|
|
|
|
function otaLayouts(row) {
|
|
if (row.hasUnknownLayout) return "Unknown layouts present";
|
|
if (row.hasDualOta && row.hasNonDualOta) return "Mixed (some lack dual OTA)";
|
|
return row.hasDualOta ? "Dual-slot OTA" : "No dual-slot OTA";
|
|
}
|
|
|
|
async function initializeViewer() {
|
|
const root = document.getElementById("partition-catalog");
|
|
if (!root || root.dataset.initialized) return;
|
|
root.dataset.initialized = "true";
|
|
const selector = document.getElementById("partition-catalog-view");
|
|
const status = document.getElementById("partition-catalog-status");
|
|
const code = document.getElementById("partition-catalog-json");
|
|
const link = document.getElementById("partition-catalog-open");
|
|
const raw = document.getElementById("partition-catalog-raw");
|
|
const search = document.getElementById("partition-catalog-search");
|
|
const tbody = document.getElementById("partition-catalog-boards");
|
|
const count = document.getElementById("partition-catalog-count");
|
|
try {
|
|
const response = await global.fetch(link.href, { cache: "no-cache" });
|
|
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
const catalog = validateCatalog(await response.json());
|
|
const render = function () {
|
|
// Catalog text is data, never HTML. Only format the large builds array
|
|
// when the reader explicitly requests the complete catalog.
|
|
code.textContent = jsonView(catalog, selector.value);
|
|
};
|
|
raw.addEventListener("toggle", function () { if (raw.open) render(); });
|
|
if (raw.open) render();
|
|
selector.disabled = false;
|
|
selector.addEventListener("change", render);
|
|
const rows = boardMinimums(catalog).map(function (board) {
|
|
const tr = document.createElement("tr");
|
|
const name = document.createElement("th");
|
|
name.scope = "row";
|
|
name.textContent = board.board;
|
|
const size = document.createElement("td");
|
|
size.textContent = formatSize(board.smallestBytes);
|
|
if (board.smallestBytes !== null) {
|
|
const bytes = document.createElement("small");
|
|
bytes.textContent = board.smallestBytes.toLocaleString("en-US") + " bytes";
|
|
size.appendChild(bytes);
|
|
}
|
|
const layout = document.createElement("td");
|
|
layout.textContent = otaLayouts(board);
|
|
tr.appendChild(name);
|
|
tr.appendChild(size);
|
|
tr.appendChild(layout);
|
|
tbody.appendChild(tr);
|
|
return { name: board.board.toLowerCase(), element: tr };
|
|
});
|
|
const filter = function () {
|
|
const query = search.value.trim().toLowerCase();
|
|
let visible = 0;
|
|
rows.forEach(function (row) {
|
|
row.element.hidden = !row.name.includes(query);
|
|
if (!row.element.hidden) visible++;
|
|
});
|
|
count.textContent = "Showing " + visible + " of " + rows.length + " boards.";
|
|
};
|
|
filter();
|
|
search.disabled = false;
|
|
search.addEventListener("input", filter);
|
|
document.getElementById("partition-catalog-table").hidden = false;
|
|
const counts = snapshot(catalog).counts;
|
|
status.textContent = "Snapshot " + catalog.generatedAt + ": " +
|
|
counts.builds.toLocaleString("en-US") + " builds, " +
|
|
counts.releases.toLocaleString("en-US") + " releases, " +
|
|
counts.layouts + " layouts, " + counts.unresolved + " unresolved assets.";
|
|
} catch (error) {
|
|
status.textContent = "Could not load the catalog: " + error.message +
|
|
". Use Open JSON or Download JSON to access the file directly.";
|
|
code.textContent = "Catalog preview unavailable.";
|
|
}
|
|
}
|
|
|
|
const api = Object.freeze({
|
|
validateCatalog, jsonView, boardMinimums, formatSize, otaLayouts, initializeViewer,
|
|
});
|
|
if (typeof module === "object" && module.exports) module.exports = api;
|
|
if (typeof document !== "undefined") {
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", initializeViewer, { once: true });
|
|
} else {
|
|
initializeViewer();
|
|
}
|
|
}
|
|
})(typeof globalThis !== "undefined" ? globalThis : this);
|