Files
Krunker-Civilian-Client-Test/src/preload/changelog.ts
T
bigjakk aeabddcf3a
Build and Release / build-and-release (push) Has been cancelled
initial commit
2026-04-03 15:33:20 -07:00

130 lines
4.7 KiB
TypeScript

// ── Changelog Popup ──
// Shows release notes in a Shadow DOM modal when the client version changes.
import { ipcRenderer } from 'electron';
function versionLessThan(a: string, b: string): boolean {
const pa = a.split('.').map(Number);
const pb = b.split('.').map(Number);
const len = Math.max(pa.length, pb.length);
for (let i = 0; i < len; i++) {
const na = pa[i] || 0;
const nb = pb[i] || 0;
if (na < nb) return true;
if (na > nb) return false;
}
return false;
}
function renderMarkdown(md: string): string {
const html = md
.replace(/### (.+)/g, '<h3>$1</h3>')
.replace(/## (.+)/g, '<h2>$1</h2>')
.replace(/# (.+)/g, '<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
// Convert list items
const lines = html.split('\n');
let inList = false;
const out: string[] = [];
for (const line of lines) {
if (line.trimStart().startsWith('- ')) {
if (!inList) { out.push('<ul>'); inList = true; }
out.push('<li>' + line.trimStart().slice(2) + '</li>');
} else {
if (inList) { out.push('</ul>'); inList = false; }
out.push(line);
}
}
if (inList) out.push('</ul>');
return out.join('\n').replace(/\n\n/g, '<br><br>').replace(/\n/g, '<br>');
}
function showChangelogPopup(version: string, body: string): void {
const host = document.createElement('div');
host.id = 'kpc-changelog-host';
const shadow = host.attachShadow({ mode: 'closed' });
const style = document.createElement('style');
style.textContent = `
.overlay {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background: rgba(0,0,0,0.75); z-index: 99998;
display: flex; justify-content: center; align-items: center;
font-family: 'Segoe UI', sans-serif; color: #e0e0e0;
}
.modal {
background: #1a1a2e; border-radius: 12px; padding: 24px;
min-width: 400px; max-width: 600px; max-height: 70vh;
display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 16px;
}
.header h2 { margin: 0; font-size: 1.4rem; color: #fff; }
.close-btn {
background: none; border: none; color: #888; font-size: 1.5rem;
cursor: pointer; padding: 4px 8px; border-radius: 4px;
}
.close-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
.body {
overflow-y: auto; flex: 1; line-height: 1.6;
}
.body h1 { font-size: 1.3rem; color: #fff; margin: 12px 0 6px; }
.body h2 { font-size: 1.15rem; color: #fff; margin: 10px 0 6px; }
.body h3 { font-size: 1rem; color: #ccc; margin: 8px 0 4px; }
.body ul { padding-left: 20px; margin: 6px 0; }
.body li { margin: 3px 0; }
.body a { color: #6ea8fe; }
.body strong { color: #fff; }
`;
const overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.addEventListener('click', (e) => {
if (e.target === overlay) host.remove();
});
const modal = document.createElement('div');
modal.className = 'modal';
const header = document.createElement('div');
header.className = 'header';
header.innerHTML = `<h2>What's New in v${version}</h2>`;
const closeBtn = document.createElement('button');
closeBtn.className = 'close-btn';
closeBtn.textContent = '\u2715';
closeBtn.addEventListener('click', () => host.remove());
header.appendChild(closeBtn);
const bodyDiv = document.createElement('div');
bodyDiv.className = 'body';
bodyDiv.innerHTML = renderMarkdown(body);
modal.appendChild(header);
modal.appendChild(bodyDiv);
overlay.appendChild(modal);
shadow.appendChild(style);
shadow.appendChild(overlay);
document.body.appendChild(host);
}
export async function checkChangelog(currentVersion: string, lastSeenVersion: string): Promise<void> {
if (lastSeenVersion && !versionLessThan(lastSeenVersion, currentVersion)) return;
// Update lastSeenVersion regardless of whether we can fetch notes
ipcRenderer.invoke('set-config', 'ui', {
...await ipcRenderer.invoke('get-config', 'ui'),
lastSeenVersion: currentVersion,
});
try {
const body = await ipcRenderer.invoke('changelog-fetch', currentVersion);
if (body) showChangelogPopup(currentVersion, body);
} catch { /* fetch failed — skip silently */ }
}