This commit is contained in:
mikecarper
2026-09-07 04:43:36 +00:00
parent 9a1b8a0fcf
commit ea3d849ed5
4 changed files with 163 additions and 4 deletions
+102 -1
View File
@@ -792,6 +792,52 @@
}).filter(Boolean)));
}
function selectionUrl(url, filters, automaticChipFamily) {
const result = new URL(url);
FACET_FIELDS.forEach(function (field) {
result.searchParams.delete(field);
if (filters[field]) result.searchParams.set(field, filters[field]);
});
result.searchParams.delete("chipAuto");
if (filters.hardwareFamily || filters.hardware) {
result.searchParams.set("chipAuto", automaticChipFamily ? "1" : "0");
}
return result.href;
}
function selectionFromUrl(url, profiles) {
const params = new URL(url).searchParams;
const requested = {};
const filters = {};
const unavailable = [];
FACET_FIELDS.forEach(function (field) {
requested[field] = params.get(field) || "";
});
// Handwritten links may specify just the exact hardware variant.
if (requested.hardware && !requested.hardwareFamily) {
const profile = profiles.find(function (item) {
return item.hardware === requested.hardware;
});
if (profile) requested.hardwareFamily = profile.hardwareFamily;
}
const automaticChipFamily = params.get("chipAuto") === "1" ||
(params.get("chipAuto") !== "0" && !requested.chipFamily &&
Boolean(requested.hardwareFamily || requested.hardware));
if (automaticChipFamily) requested.chipFamily = "";
// Validate in picker order so a stale variant cannot discard valid hardware.
FACET_FIELDS.forEach(function (field) {
const value = requested[field];
if (!value) return;
if ((field !== "hardware" || filters.hardwareFamily) &&
facetValues(profiles, filters, field).includes(value)) {
filters[field] = value;
} else {
unavailable.push(field + "=" + value);
}
});
return { filters: filters, automaticChipFamily: automaticChipFamily, unavailable: unavailable };
}
function humanizeHardware(value) {
return String(value || "").replace(/_/g, " ").replace(/\s+/g, " ").trim();
}
@@ -1249,6 +1295,9 @@
const search = root.querySelector('[data-field="asset-search"]');
const assetResults = root.querySelector('[data-role="asset-results"]');
const clearButton = form.querySelector('[data-action="clear"]');
const shareLink = root.querySelector('[data-role="share-link"]');
const copyLinkButton = root.querySelector('[data-action="copy-link"]');
const linkStatus = root.querySelector('[data-role="link-status"]');
const controls = {};
FACET_FIELDS.forEach(function (field) {
controls[field] = form.querySelector('[data-field="' + field + '"]');
@@ -1258,6 +1307,53 @@
let automaticChipFamily = false;
const groupPrefix = "firmware-picker-" + (++pickerInstanceCount);
function updateSelectionUrl() {
const url = selectionUrl(global.location.href, filters, automaticChipFamily);
if (url !== global.location.href) {
try {
global.history.replaceState(global.history.state, "", url);
} catch (error) {
// Some local-file viewers block History API writes. The share link
// still carries the complete selection on the public web picker.
}
}
if (shareLink) {
const base = global.location.protocol === "file:"
? root.getAttribute("data-share-url") : url;
shareLink.href = selectionUrl(base || url, filters, automaticChipFamily);
shareLink.hidden = false;
}
if (copyLinkButton) copyLinkButton.disabled = false;
}
function restoreSelectionUrl() {
if (!catalog.profiles.length) return;
const restored = selectionFromUrl(global.location.href, catalog.profiles);
FACET_FIELDS.forEach(function (field) {
filters[field] = restored.filters[field] || "";
});
automaticChipFamily = restored.automaticChipFamily;
if (linkStatus) linkStatus.textContent = restored.unavailable.length
? "Some linked choices are unavailable or incompatible in the current release: " +
restored.unavailable.join(", ") + ". Review the remaining choices before downloading."
: "";
refreshFacets();
}
if (copyLinkButton && shareLink) copyLinkButton.addEventListener("click", function () {
const copy = global.navigator.clipboard && global.navigator.clipboard.writeText;
if (!copy) {
linkStatus.textContent = "Copy the address bar, or copy the Link to these settings link.";
return;
}
global.navigator.clipboard.writeText(shareLink.href).then(function () {
linkStatus.textContent = "Link copied.";
}).catch(function () {
linkStatus.textContent = "Copy the address bar, or copy the Link to these settings link.";
});
});
global.addEventListener("popstate", restoreSelectionUrl);
function matchingProfiles(ignoredField) {
return catalog.profiles.filter(function (profile) {
return profileMatchesFacets(profile, filters, ignoredField);
@@ -1387,6 +1483,7 @@
if (chipSummary) chipSummary.textContent = "Optional: chip family" +
(filters.chipFamily ? " — " + labelFor("chipFamily", filters.chipFamily) : "");
render();
updateSelectionUrl();
}
function render() {
@@ -1498,6 +1595,7 @@
const target = event.target;
const field = target.dataset.choiceField || target.dataset.field;
if (!FACET_FIELDS.includes(field)) return;
if (linkStatus) linkStatus.textContent = "";
if (field === "chipFamily") {
automaticChipFamily = false;
// A deliberate family switch takes precedence over an old board.
@@ -1519,6 +1617,7 @@
event.preventDefault();
if (!catalog.profiles.length) return;
automaticChipFamily = false;
if (linkStatus) linkStatus.textContent = "";
FACET_FIELDS.forEach(function (field) {
filters[field] = "";
});
@@ -1572,7 +1671,7 @@
releaseSetStatus.appendChild(link);
}
clearButton.disabled = false;
refreshFacets();
restoreSelectionUrl();
})
.catch(function (error) {
status.textContent =
@@ -1619,6 +1718,8 @@
profileMatchesFacets: profileMatchesFacets,
facetValues: facetValues,
uniqueValues: uniqueValues,
selectionUrl: selectionUrl,
selectionFromUrl: selectionFromUrl,
canonicalAsset: canonicalAsset,
resolveProfileAssets: resolveProfileAssets,
shouldShowCandidateResults: shouldShowCandidateResults,
+13
View File
@@ -144,6 +144,19 @@
grid-column: 1 / -1;
}
.firmware-picker-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.65rem;
}
.firmware-picker-form-actions [data-role="link-status"] {
flex-basis: 100%;
margin: 0;
overflow-wrap: anywhere;
}
.firmware-picker-form-actions button {
min-height: 2.35rem;
padding: 0.4rem 0.75rem;
+47 -2
View File
@@ -624,6 +624,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#share-a-selection" class="md-nav__link">
<span class="md-ellipsis">
Share a selection
</span>
</a>
</li>
<li class="md-nav__item">
@@ -1617,6 +1628,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#share-a-selection" class="md-nav__link">
<span class="md-ellipsis">
Share a selection
</span>
</a>
</li>
<li class="md-nav__item">
@@ -1714,7 +1736,7 @@ hardware list first. You can skip it: picking hardware fills it in automatically
<p>The picker reads public release metadata from GitHub. It does not upload device
information. Hardware names, target names, and download links come directly
from the published firmware assets.</p>
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json">
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json" data-share-url="https://mikecarper.github.io/MeshCore/firmware_picker/">
<div class="firmware-picker-intro" role="note">
<strong>Current release set</strong>
<p data-role="release-set">Loading release information...</p>
@@ -1728,7 +1750,8 @@ from the published firmware assets.</p>
<p class="firmware-picker-order-note">
Pick in any order. Use <strong>Any</strong> to clear one choice, or clear
everything with the button below.
everything with the button below. The address bar updates with your choices;
copy its URL to reopen or share the same selection.
</p>
<form class="firmware-picker-form" data-role="form">
@@ -1808,6 +1831,9 @@ from the published firmware assets.</p>
<div class="firmware-picker-form-actions firmware-picker-wide">
<button type="reset" data-action="clear" disabled>Clear all choices</button>
<button type="button" data-action="copy-link" disabled>Copy link to settings</button>
<a data-role="share-link" hidden>Link to these settings</a>
<p data-role="link-status" aria-live="polite"></p>
</div>
</form>
@@ -1917,6 +1943,25 @@ For Heltec V4 specifically, <code>companion_radio_full</code> is still a LoRa-pr
Companion; choose the existing <code>heltec_v4_repeater_bridge_espnow</code> firmware to
make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
changing roles or partition layouts.</p>
<h2 id="share-a-selection">Share a selection</h2>
<p>Select any combination of choices, then copy the address bar or use <strong>Copy link
to settings</strong>. Opening the link restores those choices after the release
catalog loads. Partial selections work too, so you can share a board and role
while leaving other choices open. <strong>Clear all choices</strong> removes the picker
parameters from the URL. Changes update the current browser-history entry
without reloading the page or adding a Back-button entry for every click.</p>
<p>For example, <a href="?hardware=RAK_3401&amp;role=repeater&amp;ota=lora-receiver&amp;variant=no-external-sensors">RAK3401 repeater with internal storage</a>
preselects that board, role, OTA capability, and storage profile.</p>
<p>The query parameters are <code>chipFamily</code>, <code>hardwareFamily</code>, <code>hardware</code>, <code>role</code>,
<code>logging</code>, <code>ota</code>, <code>mode</code>, <code>feature</code>, <code>variant</code>, and <code>install</code>. Values use the
picker's internal identifiers rather than the displayed labels. The generated
link also records <code>chipAuto</code> so automatic chip-family selection or an explicit
<strong>Any</strong> choice behaves the same after reopening. Existing section anchors and
unrelated query parameters are preserved.</p>
<p>Links use the current release catalog. If a linked choice is no longer available
or conflicts with another choice, the picker identifies it and asks you to
review the remaining selections. The downloadable HTML's <strong>Copy link to
settings</strong> button creates a public website link that other people can open.</p>
<h2 id="full-versus-standard">FULL versus standard</h2>
<p>For a new installation, use the FULL / complete profile when it exists and the
board has enough flash. FULL profiles keep the complete supported feature set
File diff suppressed because one or more lines are too long