better build menu

This commit is contained in:
KingKDot
2026-08-01 02:27:35 -05:00
parent b985aea80d
commit fe38187754
2 changed files with 259 additions and 18 deletions
+159 -14
View File
@@ -15,6 +15,12 @@ const buildStatus = document.getElementById("build-status");
const buildStatusText = document.getElementById("build-status-text"); const buildStatusText = document.getElementById("build-status-text");
const buildOutputDiv = document.getElementById("build-output"); const buildOutputDiv = document.getElementById("build-output");
const buildOutputContainer = document.getElementById("build-output-container"); const buildOutputContainer = document.getElementById("build-output-container");
const buildLogIssueCount = document.getElementById("build-log-issue-count");
const buildLogFilterButtons = document.querySelectorAll("[data-build-log-filter]");
const buildLogWrapBtn = document.getElementById("build-log-wrap-btn");
const buildLogFollowBtn = document.getElementById("build-log-follow-btn");
const buildLogCopyBtn = document.getElementById("build-log-copy-btn");
const buildLogClearBtn = document.getElementById("build-log-clear-btn");
const buildResults = document.getElementById("build-results"); const buildResults = document.getElementById("build-results");
const buildFilesDiv = document.getElementById("build-files"); const buildFilesDiv = document.getElementById("build-files");
const logoutBtn = document.getElementById("logout-btn"); const logoutBtn = document.getElementById("logout-btn");
@@ -199,6 +205,147 @@ function updateServerUrlCurrentButton() {
} }
let isBuilding = false; let isBuilding = false;
const BUILD_LOG_MAX_VISIBLE_LINES = 5000;
const BUILD_LOG_WRAP_KEY = "overlord.buildLog.wrap.v1";
let buildLogRawChunks = [];
let buildLogLineCount = 0;
let buildLogIssueTotal = 0;
let buildLogFollow = true;
function updateBuildLogSummary() {
if (buildLogIssueCount) buildLogIssueCount.textContent = buildLogIssueTotal.toLocaleString();
}
function setBuildLogFollow(enabled) {
buildLogFollow = enabled;
buildLogFollowBtn?.setAttribute("aria-pressed", String(enabled));
if (enabled && buildOutputContainer) {
buildOutputContainer.scrollTop = buildOutputContainer.scrollHeight;
}
}
function resetBuildLog(showEmptyState = false) {
buildLogRawChunks = [];
buildLogLineCount = 0;
buildLogIssueTotal = 0;
if (buildOutputDiv) {
buildOutputDiv.innerHTML = showEmptyState
? '<div class="build-log-empty">Output cleared. New build events will appear here.</div>'
: "";
}
updateBuildLogSummary();
}
function classifyBuildLogLine(line, requestedLevel) {
const normalizedLevel = String(requestedLevel || "info").toLowerCase();
if (normalizedLevel === "error" || /\b(error|failed|failure|fatal|panic)\b/i.test(line)) return "error";
if (normalizedLevel === "warn" || normalizedLevel === "warning" || /\b(warn(?:ing)?|retry|skipp?ed|hint)\b/i.test(line)) return "warning";
if (normalizedLevel === "success" || /\b(success(?:ful(?:ly)?)?|completed|\[ok\])\b/i.test(line)) return "success";
if (/^(?:\s*[=─-]{2,}|\s*\[[^\]]+\]|\s*(?:starting|build id|building|uploading|pushing|reconnecting)\b)/i.test(line)) return "stage";
return "detail";
}
function appendBuildLogLine(line, requestedLevel) {
if (!buildOutputDiv || !line.trim()) return;
buildOutputDiv.querySelector(".build-log-empty")?.remove();
const level = classifyBuildLogLine(line, requestedLevel);
const row = document.createElement("div");
row.className = "build-log-line";
row.dataset.logLevel = level;
buildLogLineCount += 1;
if (level === "warning" || level === "error") buildLogIssueTotal += 1;
const number = document.createElement("span");
number.className = "build-log-number";
number.textContent = String(buildLogLineCount);
const content = document.createElement("span");
content.className = "build-log-content";
content.appendChild(document.createTextNode(line));
row.appendChild(number);
row.appendChild(content);
buildOutputDiv.appendChild(row);
while (buildOutputDiv.children.length > BUILD_LOG_MAX_VISIBLE_LINES) {
buildOutputDiv.firstElementChild?.remove();
}
if (level !== "detail") {
animateElement(row, [
{ opacity: 0, transform: "translateX(5px)" },
{ opacity: 1, transform: "translateX(0)" },
], { duration: 170 });
}
}
function initBuildConsole() {
let wrapEnabled = false;
try { wrapEnabled = localStorage.getItem(BUILD_LOG_WRAP_KEY) === "1"; } catch {}
buildOutputDiv?.classList.toggle("log-wrap", wrapEnabled);
buildLogWrapBtn?.setAttribute("aria-pressed", String(wrapEnabled));
buildLogFilterButtons.forEach((button) => {
button.addEventListener("click", () => {
const filter = button.dataset.buildLogFilter || "all";
buildOutputDiv.dataset.filter = filter;
buildLogFilterButtons.forEach((candidate) => {
candidate.setAttribute("aria-pressed", String(candidate === button));
});
});
});
buildLogWrapBtn?.addEventListener("click", () => {
const enabled = !buildOutputDiv.classList.contains("log-wrap");
buildOutputDiv.classList.toggle("log-wrap", enabled);
buildLogWrapBtn.setAttribute("aria-pressed", String(enabled));
try { localStorage.setItem(BUILD_LOG_WRAP_KEY, enabled ? "1" : "0"); } catch {}
});
buildLogFollowBtn?.addEventListener("click", () => setBuildLogFollow(!buildLogFollow));
buildOutputContainer?.addEventListener("scroll", () => {
const distanceFromBottom = buildOutputContainer.scrollHeight
- buildOutputContainer.scrollTop
- buildOutputContainer.clientHeight;
if (distanceFromBottom > 40 && buildLogFollow) setBuildLogFollow(false);
}, { passive: true });
buildLogCopyBtn?.addEventListener("click", async () => {
const text = buildLogRawChunks.join("");
if (!text) return;
let copied = false;
try {
await navigator.clipboard.writeText(text);
copied = true;
} catch {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
copied = document.execCommand?.("copy") === true;
textarea.remove();
}
if (copied) {
buildLogCopyBtn.innerHTML = '<i class="fa-solid fa-check text-emerald-400"></i><span class="sr-only">Copied</span>';
setTimeout(() => {
buildLogCopyBtn.innerHTML = '<i class="fa-regular fa-copy"></i><span class="sr-only">Copy output</span>';
}, 1400);
} else {
window.showToast?.("Could not copy build output", "error");
}
});
buildLogClearBtn?.addEventListener("click", () => {
resetBuildLog(true);
setBuildLogFollow(true);
});
updateBuildLogSummary();
}
initBuildConsole();
function revealBuildResults() { function revealBuildResults() {
if (buildResults.classList.contains("hidden")) { if (buildResults.classList.contains("hidden")) {
@@ -2053,7 +2200,8 @@ async function startBuild(config) {
buildResults.classList.add("hidden"); buildResults.classList.add("hidden");
buildFilesDiv.innerHTML = ""; buildFilesDiv.innerHTML = "";
buildOutputDiv.innerHTML = ""; resetBuildLog();
setBuildLogFollow(true);
addBuildOutput("Starting build process...\n", "info"); addBuildOutput("Starting build process...\n", "info");
try { try {
@@ -2298,7 +2446,6 @@ async function streamBuildOutput(buildId, config = {}) {
} }
} }
buildOutputContainer.scrollTop = buildOutputContainer.scrollHeight;
} }
// Stream ended cleanly without a "complete" event — build may still be running // Stream ended cleanly without a "complete" event — build may still be running
completed = true; completed = true;
@@ -2340,20 +2487,18 @@ async function streamBuildOutput(buildId, config = {}) {
} }
function addBuildOutput(text, level = "info") { function addBuildOutput(text, level = "info") {
const span = document.createElement("span"); const rawText = String(text ?? "");
span.textContent = text; if (!rawText) return;
buildLogRawChunks.push(rawText);
const normalized = rawText.replace(/\r\n?/g, "\n");
normalized.split("\n").forEach((line) => appendBuildLogLine(line, level));
updateBuildLogSummary();
if (level === "error") { if (buildLogFollow && buildOutputContainer) {
span.className = "text-red-400"; requestAnimationFrame(() => {
} else if (level === "success") { buildOutputContainer.scrollTop = buildOutputContainer.scrollHeight;
span.className = "text-green-400"; });
} else if (level === "warn") {
span.className = "text-yellow-400";
} else {
span.className = "text-slate-300";
} }
buildOutputDiv.appendChild(span);
} }
function renderShareLinksPanel(items) { function renderShareLinksPanel(items) {
+100 -4
View File
@@ -79,6 +79,78 @@
background: linear-gradient(135deg, rgba(37, 99, 235, 0.95), rgba(79, 70, 229, 0.9)); background: linear-gradient(135deg, rgba(37, 99, 235, 0.95), rgba(79, 70, 229, 0.9));
box-shadow: 0 8px 22px rgba(37, 99, 235, 0.22); box-shadow: 0 8px 22px rgba(37, 99, 235, 0.22);
} }
.build-console {
overflow: hidden;
border: 1px solid rgb(51 65 85);
border-radius: 0.75rem;
background: rgb(2 6 23 / 0.92);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.025);
}
.build-console-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.65rem;
padding: 0.6rem 0.75rem;
border-bottom: 1px solid rgb(30 41 59);
background: linear-gradient(180deg, rgb(15 23 42 / 0.96), rgb(8 15 30 / 0.96));
}
.build-console-group { display: flex; align-items: center; gap: 0.35rem; }
.build-console-control {
display: inline-flex;
align-items: center;
gap: 0.35rem;
min-height: 1.9rem;
padding: 0.3rem 0.55rem;
border: 1px solid transparent;
border-radius: 0.45rem;
color: rgb(148 163 184);
font-size: 0.7rem;
font-weight: 600;
}
.build-console-control:hover { color: rgb(226 232 240); background: rgb(51 65 85 / 0.5); }
.build-console-control[aria-pressed="true"] {
color: rgb(241 245 249);
border-color: rgb(100 116 139 / 0.45);
background: rgb(51 65 85 / 0.72);
}
#build-output-container {
min-height: 28rem;
max-height: calc(100vh - 23rem);
overflow: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
#build-output { min-width: max-content; padding: 0.55rem 0; }
#build-output.log-wrap { min-width: 0; }
.build-log-line {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr);
min-height: 1.65rem;
border-left: 2px solid transparent;
color: rgb(148 163 184);
font-family: inherit;
font-size: 0.78rem;
line-height: 1.55;
}
.build-log-line:hover { background: rgb(30 41 59 / 0.34); }
.build-log-number {
padding: 0.15rem 0.65rem 0.15rem 0.35rem;
color: rgb(71 85 105);
text-align: right;
user-select: none;
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
}
.build-log-content { padding: 0.15rem 0.8rem 0.15rem 0; white-space: pre; }
#build-output.log-wrap .build-log-content { white-space: pre-wrap; overflow-wrap: anywhere; }
.build-log-line[data-log-level="stage"] { color: rgb(186 230 253); border-left-color: rgb(56 189 248); background: rgb(14 165 233 / 0.055); }
.build-log-line[data-log-level="success"] { color: rgb(134 239 172); border-left-color: rgb(34 197 94); background: rgb(22 163 74 / 0.06); }
.build-log-line[data-log-level="warning"] { color: rgb(253 224 71); border-left-color: rgb(234 179 8); background: rgb(202 138 4 / 0.06); }
.build-log-line[data-log-level="error"] { color: rgb(253 164 175); border-left-color: rgb(244 63 94); background: rgb(225 29 72 / 0.08); }
#build-output[data-filter="important"] .build-log-line[data-log-level="detail"],
#build-output[data-filter="issues"] .build-log-line:not([data-log-level="warning"]):not([data-log-level="error"]) { display: none; }
.build-log-empty { padding: 1.2rem; color: rgb(100 116 139); font-size: 0.78rem; }
[data-builder-panel][hidden] { display: none !important; } [data-builder-panel][hidden] { display: none !important; }
.builder-profile-bar { .builder-profile-bar {
display: grid; display: grid;
@@ -1244,10 +1316,34 @@
</div> </div>
</div> </div>
<div id="build-output-container" <div class="build-console">
class="bg-slate-950 border border-slate-700 rounded-lg p-4 min-h-[28rem] max-h-[calc(100vh-20rem)] overflow-y-auto font-mono text-sm"> <div class="build-console-toolbar" aria-label="Build output controls">
<div id="build-output" class="text-slate-400"> <div class="build-console-group" role="group" aria-label="Filter build output">
Ready to build. Select platforms and click "Start Build". <button type="button" class="build-console-control" data-build-log-filter="all" aria-pressed="true">All</button>
<button type="button" class="build-console-control" data-build-log-filter="important" aria-pressed="false">Important</button>
<button type="button" class="build-console-control" data-build-log-filter="issues" aria-pressed="false">
Issues <span id="build-log-issue-count" class="text-amber-300">0</span>
</button>
</div>
<div class="build-console-group">
<button id="build-log-wrap-btn" type="button" class="build-console-control" aria-pressed="false" title="Wrap long lines">
<i class="fa-solid fa-align-left"></i><span>Wrap</span>
</button>
<button id="build-log-follow-btn" type="button" class="build-console-control" aria-pressed="true" title="Follow new output">
<i class="fa-solid fa-arrow-down"></i><span>Follow</span>
</button>
<button id="build-log-copy-btn" type="button" class="build-console-control" title="Copy full output">
<i class="fa-regular fa-copy"></i><span class="sr-only">Copy output</span>
</button>
<button id="build-log-clear-btn" type="button" class="build-console-control" title="Clear output">
<i class="fa-solid fa-eraser"></i><span class="sr-only">Clear output</span>
</button>
</div>
</div>
<div id="build-output-container">
<div id="build-output" class="text-slate-400" data-filter="all" role="log" aria-live="polite" aria-relevant="additions">
<div class="build-log-empty">Ready to build. Select platforms and click “Start Build”.</div>
</div>
</div> </div>
</div> </div>