Redesign updater UI with manual version picker and status bar

This commit is contained in:
Aaron
2025-12-14 18:48:00 -05:00
parent b01bfcd38e
commit a94cd17186
3 changed files with 154 additions and 33 deletions

View File

@@ -94,6 +94,60 @@
text-align: right; text-align: right;
} }
.release-status-bar {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 6px;
}
.release-advanced {
border: 1px dashed var(--border);
border-radius: 12px;
padding: 10px;
margin-top: 8px;
background: rgba(255, 255, 255, 0.02);
}
.release-advanced-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 6px;
}
.release-list {
display: grid;
gap: 8px;
}
.release-card {
border: 1px solid var(--border);
border-radius: 10px;
padding: 8px 10px;
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
align-items: center;
cursor: pointer;
}
.release-card input[type="radio"] {
accent-color: var(--accent);
}
.release-card-title {
font-weight: 600;
}
.release-card-tags {
display: flex;
gap: 8px;
align-items: center;
}
.modal-card .status-msg { .modal-card .status-msg {
overflow-wrap: anywhere; overflow-wrap: anywhere;
margin-top: 6px; margin-top: 6px;

View File

@@ -25,9 +25,14 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction, lo
const releaseProgress = document.getElementById("releaseProgress"); const releaseProgress = document.getElementById("releaseProgress");
const releaseCheckBtn = document.getElementById("releaseCheckBtn"); const releaseCheckBtn = document.getElementById("releaseCheckBtn");
const releaseApplyBtn = document.getElementById("releaseApplyBtn"); const releaseApplyBtn = document.getElementById("releaseApplyBtn");
const releaseVersionSelect = document.getElementById("releaseVersionSelect"); const releaseAdvancedToggle = document.getElementById("releaseAdvancedToggle");
const releaseAdvanced = document.getElementById("releaseAdvanced");
const releaseList = document.getElementById("releaseList");
const releaseApplyVersionBtn = document.getElementById("releaseApplyVersionBtn"); const releaseApplyVersionBtn = document.getElementById("releaseApplyVersionBtn");
const releaseAutoCheck = document.getElementById("releaseAutoCheck"); const releaseAutoCheck = document.getElementById("releaseAutoCheck");
const releaseStatusChip = document.getElementById("releaseStatusChip");
const releaseChannelChip = document.getElementById("releaseChannelChip");
const releaseLastCheckChip = document.getElementById("releaseLastCheckChip");
const releaseLog = document.getElementById("releaseLog"); const releaseLog = document.getElementById("releaseLog");
const releaseLogStatus = document.getElementById("releaseLogStatus"); const releaseLogStatus = document.getElementById("releaseLogStatus");
const releaseLogCopy = document.getElementById("releaseLogCopy"); const releaseLogCopy = document.getElementById("releaseLogCopy");
@@ -68,38 +73,68 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction, lo
}; };
async function loadReleaseList() { async function loadReleaseList() {
if (!releaseVersionSelect) return; if (!releaseList) return;
try { try {
const data = await listReleases(); const data = await listReleases();
releaseOptions = data.releases || []; releaseOptions = data.releases || [];
releaseVersionSelect.innerHTML = ""; renderReleaseList();
if (!releaseOptions.length) { } catch (e) {
const opt = document.createElement("option"); renderReleaseList(true);
opt.value = ""; }
opt.textContent = "No releases found"; }
releaseVersionSelect.appendChild(opt);
releaseVersionSelect.disabled = true; function renderReleaseList(error = false) {
releaseApplyVersionBtn && (releaseApplyVersionBtn.disabled = true); if (!releaseList) return;
releaseList.innerHTML = "";
if (error) {
releaseList.textContent = "Failed to load releases.";
return; return;
} }
releaseVersionSelect.disabled = false; if (!releaseOptions.length) {
releaseOptions.forEach((r) => { releaseList.textContent = "No releases found.";
const opt = document.createElement("option"); return;
opt.value = r.version; }
const tag = r.prerelease ? " (dev)" : ""; releaseOptions.forEach((r, idx) => {
opt.textContent = `${r.version}${tag}${r.published_at ? `${fmtDate(r.published_at)}` : ""}`; const card = document.createElement("label");
releaseVersionSelect.appendChild(opt); card.className = "release-card";
card.setAttribute("role", "option");
const input = document.createElement("input");
input.type = "radio";
input.name = "releaseVersion";
input.value = r.version;
if (idx === 0) input.checked = true;
const meta = document.createElement("div");
meta.className = "release-card-meta";
const title = document.createElement("div");
title.className = "release-card-title";
title.textContent = r.version;
const tags = document.createElement("div");
tags.className = "release-card-tags";
const chip = document.createElement("span");
chip.className = "status-chip ghost";
chip.textContent = r.prerelease ? "Dev" : "Stable";
tags.appendChild(chip);
if (r.published_at) {
const date = document.createElement("span");
date.className = "hint quiet";
date.textContent = fmtDate(r.published_at);
tags.appendChild(date);
}
meta.appendChild(title);
meta.appendChild(tags);
if (r.changelog_url) {
const link = document.createElement("a");
link.href = r.changelog_url;
link.target = "_blank";
link.className = "hint";
link.textContent = "Changelog";
meta.appendChild(link);
}
card.appendChild(input);
card.appendChild(meta);
releaseList.appendChild(card);
}); });
if (releaseApplyVersionBtn) releaseApplyVersionBtn.disabled = false; if (releaseApplyVersionBtn) releaseApplyVersionBtn.disabled = false;
} catch (e) {
releaseVersionSelect.innerHTML = "";
const opt = document.createElement("option");
opt.value = "";
opt.textContent = "Failed to load releases";
releaseVersionSelect.appendChild(opt);
releaseVersionSelect.disabled = true;
if (releaseApplyVersionBtn) releaseApplyVersionBtn.disabled = true;
}
} }
function setReleaseChip(state) { function setReleaseChip(state) {
@@ -193,6 +228,7 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction, lo
current_release_date = null, current_release_date = null,
latest_release_date = null, latest_release_date = null,
changelog_url = null, changelog_url = null,
last_check = null,
} = data || {}; } = data || {};
releaseChannel = channel || "dev"; releaseChannel = channel || "dev";
if (releaseChannelToggle) releaseChannelToggle.checked = releaseChannel === "dev"; if (releaseChannelToggle) releaseChannelToggle.checked = releaseChannel === "dev";
@@ -214,6 +250,13 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction, lo
if (releaseLatest) releaseLatest.textContent = latest_version; if (releaseLatest) releaseLatest.textContent = latest_version;
if (releaseCurrentDate) releaseCurrentDate.textContent = fmtDate(current_release_date); if (releaseCurrentDate) releaseCurrentDate.textContent = fmtDate(current_release_date);
if (releaseLatestDate) releaseLatestDate.textContent = fmtDate(latest_release_date); if (releaseLatestDate) releaseLatestDate.textContent = fmtDate(latest_release_date);
if (releaseStatusChip) {
releaseStatusChip.textContent = `Status: ${status.replaceAll("_", " ")}`;
releaseStatusChip.classList.toggle("chip-warm", status === "update_available");
releaseStatusChip.classList.toggle("chip-off", status === "error");
}
if (releaseChannelChip) releaseChannelChip.textContent = `Channel: ${releaseChannel}`;
if (releaseLastCheckChip) releaseLastCheckChip.textContent = `Last check: ${last_check ? fmtDate(last_check) : "—"}`;
if (releaseAutoCheck) releaseAutoCheck.checked = !!auto_check; if (releaseAutoCheck) releaseAutoCheck.checked = !!auto_check;
if (releaseProgress) releaseProgress.textContent = ""; if (releaseProgress) releaseProgress.textContent = "";
if (status === "in_progress" && progress) { if (status === "in_progress" && progress) {
@@ -338,14 +381,22 @@ export function initReleaseUI({ showToast, showBusy, hideBusy, confirmAction, lo
} }
}); });
releaseAdvancedToggle?.addEventListener("click", async () => {
releaseAdvanced?.classList.toggle("hidden");
if (!releaseAdvanced?.classList.contains("hidden")) {
await loadReleaseList();
}
});
releaseApplyVersionBtn?.addEventListener("click", async () => { releaseApplyVersionBtn?.addEventListener("click", async () => {
if (!releaseVersionSelect || !releaseVersionSelect.value) { const selected = releaseList?.querySelector("input[name='releaseVersion']:checked");
if (!selected) {
showToast("Select a version first", "error"); showToast("Select a version first", "error");
return; return;
} }
try { try {
lastReleaseToastKey = null; lastReleaseToastKey = null;
const ver = releaseVersionSelect.value; const ver = selected.value;
logUi(`Install version ${ver} requested`); logUi(`Install version ${ver} requested`);
releaseBusyActive = true; releaseBusyActive = true;
showBusy(`Installing ${ver}`, "Applying selected release. This can take up to a minute."); showBusy(`Installing ${ver}`, "Applying selected release. This can take up to a minute.");

View File

@@ -144,6 +144,11 @@
</button> </button>
</div> </div>
<div class="controls column"> <div class="controls column">
<div class="release-status-bar">
<span id="releaseStatusChip" class="status-chip quiet">Status: n/a</span>
<span id="releaseChannelChip" class="status-chip quiet">Channel: n/a</span>
<span id="releaseLastCheckChip" class="status-chip quiet">Last check: —</span>
</div>
<div class="control-card release-versions"> <div class="control-card release-versions">
<div> <div>
<p class="hint quiet">Current version</p> <p class="hint quiet">Current version</p>
@@ -165,9 +170,8 @@
<button id="releaseApplyBtn" title="Download and install the latest release"> <button id="releaseApplyBtn" title="Download and install the latest release">
Upgrade Upgrade
</button> </button>
<select id="releaseVersionSelect" class="ghost" title="Select a specific release to install"></select> <button id="releaseAdvancedToggle" class="ghost" title="Open manual version picker">
<button id="releaseApplyVersionBtn" class="ghost" title="Install the selected release"> Manual selection
Install version
</button> </button>
<label class="checkbox-row inline"> <label class="checkbox-row inline">
<input type="checkbox" id="releaseAutoCheck" /> <input type="checkbox" id="releaseAutoCheck" />
@@ -178,6 +182,18 @@
<span>Allow dev builds</span> <span>Allow dev builds</span>
</label> </label>
</div> </div>
<div id="releaseAdvanced" class="release-advanced hidden">
<div class="release-advanced-head">
<div>
<p class="hint quiet">Choose a specific release</p>
<span class="hint">Dev builds only appear when “Allow dev builds” is on.</span>
</div>
<button id="releaseApplyVersionBtn" class="ghost" title="Install selected release">
Install selected
</button>
</div>
<div id="releaseList" class="release-list" role="listbox" aria-label="Available releases"></div>
</div>
<div id="releaseProgress" class="hint status-msg"></div> <div id="releaseProgress" class="hint status-msg"></div>
<div class="log-card"> <div class="log-card">
<div class="log-header"> <div class="log-header">