Upload via GUI (39 Dateien)
This commit is contained in:
+578
-25
@@ -608,9 +608,12 @@ async function runModpackImport() {
|
||||
await reload();
|
||||
hide('modal-import');
|
||||
toast(`Modpack importiert: ${res.instance.name} (${res.fileCount} Dateien)`);
|
||||
// CurseForge gibt nicht jede Datei heraus – ehrlich benennen statt verschweigen
|
||||
if (res.fehlend) toast(`${res.fehlend} Datei(en) konnten nicht geladen werden und fehlen in der Instanz.`, true);
|
||||
return;
|
||||
}
|
||||
toast(res.message || 'Modpack-Import fehlgeschlagen.', true);
|
||||
if (res.needsKey) { hide('modal-import'); openSettings('pane-network'); }
|
||||
} finally {
|
||||
status('Bereit'); el('status-right').textContent = '';
|
||||
el('import-modpack-do').disabled = false;
|
||||
@@ -960,19 +963,40 @@ function formatVersionLabel(version) {
|
||||
// ---------- Automatische Updates ----------
|
||||
let pendingUpdate = null;
|
||||
|
||||
const CHANNEL_LABEL = { portable: 'Portable', setup: 'Setup' };
|
||||
|
||||
function showUpdateDialog(info) {
|
||||
pendingUpdate = info;
|
||||
const channel = CHANNEL_LABEL[info.installKind] || null;
|
||||
el('upd-current').textContent = formatVersionLabel(info.currentVersion);
|
||||
el('upd-new').textContent = formatVersionLabel(info.latestVersion);
|
||||
el('upd-channel').textContent = `Update-Kanal: ${info.installKind === 'portable' ? 'Portable' : 'Setup'}`;
|
||||
el('upd-channel').textContent = channel
|
||||
? `Update-Kanal: ${channel}`
|
||||
: 'Update-Kanal: unbekannt – automatisches Update nicht möglich';
|
||||
el('upd-notes').textContent = (info.notes || '').trim() || 'Keine Änderungshinweise hinterlegt.';
|
||||
el('upd-progress').classList.add('hidden');
|
||||
el('upd-bar-fill').style.width = '0%';
|
||||
el('upd-install').disabled = !info.asset;
|
||||
el('upd-install').textContent = info.asset ? 'Installieren' : `Kein passendes ${info.installKind === 'portable' ? 'Portable' : 'Setup'}-Update im Release`;
|
||||
|
||||
// Ohne passendes Paket wird bewusst nichts installiert – ein Portable-Launcher
|
||||
// darf nie einen Installer bekommen und umgekehrt. Statt eines toten Knopfes
|
||||
// führen wir dann zum Release, damit der Nutzer nicht in der Sackgasse steht.
|
||||
const hatPaket = !!info.asset;
|
||||
el('upd-install').classList.toggle('hidden', !hatPaket);
|
||||
el('upd-install').disabled = !hatPaket;
|
||||
el('upd-install').textContent = 'Installieren';
|
||||
el('upd-open').classList.toggle('hidden', hatPaket || !info.url);
|
||||
el('upd-hint').textContent = hatPaket
|
||||
? 'Der Launcher lädt das Update herunter und startet die Installation automatisch.'
|
||||
: (channel
|
||||
? `Für den Kanal ${channel} liegt in diesem Release keine passende Datei bereit. Bitte im Release nachsehen und von Hand aktualisieren.`
|
||||
: 'Die Paketart dieses Launchers lässt sich nicht sicher bestimmen. Bitte das Update von Hand aus dem Release laden.');
|
||||
show('modal-update');
|
||||
}
|
||||
|
||||
function openReleasePage() {
|
||||
if (pendingUpdate && pendingUpdate.url) window.api.openExternal(pendingUpdate.url);
|
||||
}
|
||||
|
||||
async function startUpdate() {
|
||||
if (!pendingUpdate || !pendingUpdate.asset) return;
|
||||
const btn = el('upd-install');
|
||||
@@ -983,7 +1007,9 @@ async function startUpdate() {
|
||||
|
||||
const res = await window.api.updateInstall(pendingUpdate.asset);
|
||||
if (res.ok) {
|
||||
el('upd-status').textContent = 'Installation wird gestartet – der Launcher schließt sich.';
|
||||
el('upd-status').textContent = res.kind === 'portable'
|
||||
? 'Launcher schließt sich, wird ersetzt und startet automatisch neu.'
|
||||
: 'Installation wird gestartet – der Launcher schließt sich.';
|
||||
} else {
|
||||
el('upd-status').textContent = '✖ ' + (res.message || 'Update fehlgeschlagen.');
|
||||
btn.disabled = false;
|
||||
@@ -1033,7 +1059,7 @@ async function applySkinBackground() {
|
||||
// Jede Pose hat auch eine Entsprechung beim 3D-Dienst (Kamerawinkel/Ansicht),
|
||||
// damit die Auswahl immer sichtbar wirkt – auch wenn der Pose-Dienst offline ist.
|
||||
// 1) Skin lokal in 3D rendern – unabhängig von Render-Diensten
|
||||
const local = await renderSkin3D(uuid, s.bgSkinPose || 'walking');
|
||||
const local = await renderSkin3D(`https://crafthead.net/skin/${uuid}`, s.bgSkinPose || 'walking');
|
||||
if (local) { root.style.setProperty('--bg-skin', `url("${local}")`); return; }
|
||||
|
||||
// 2) Rückfall: externe Render-Dienste
|
||||
@@ -1108,13 +1134,22 @@ const SKIN_POSES = {
|
||||
};
|
||||
|
||||
// Rendert den Skin lokal mit WebGL in der gewählten Pose
|
||||
async function renderSkin3D(uuid, poseKey) {
|
||||
/*
|
||||
* Rendert eine Skin-Textur als Spielfigur und gibt ein Bild zurück.
|
||||
* quelle: Adresse ODER data:-URI – so lässt sich auch eine Datei anzeigen,
|
||||
* die noch gar nicht hochgeladen ist.
|
||||
* modell: 'default' (breite Arme) oder 'slim' (Alex)
|
||||
*/
|
||||
async function renderSkin3D(quelle, poseKey, opts = {}) {
|
||||
if (typeof skinview3d === 'undefined') return null;
|
||||
const canvas = el('skin-canvas');
|
||||
const breite = opts.breite || 420;
|
||||
const hoehe = opts.hoehe || 700;
|
||||
try {
|
||||
const viewer = new skinview3d.SkinViewer({
|
||||
canvas, width: 420, height: 700,
|
||||
skin: `https://crafthead.net/skin/${uuid}`,
|
||||
canvas, width: breite, height: hoehe,
|
||||
skin: quelle,
|
||||
model: opts.modell === 'slim' ? 'slim' : 'default',
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
// Auf das Laden der Skin-Textur warten
|
||||
@@ -1179,7 +1214,7 @@ function applyIcons(root = document) {
|
||||
|
||||
// ---------- Instanz-Kacheln ----------
|
||||
const SINGLE_SELECTION_BUTTON_IDS = [
|
||||
'p-play', 'p-offline', 'p-edit', 'p-notes', 'p-mods', 'p-worlds', 'p-screens',
|
||||
'p-play', 'p-offline', 'p-edit', 'p-notes', 'p-mods', 'p-worlds', 'p-screens', 'p-logs', 'p-servers',
|
||||
'p-mcfolder', 'p-configfolder', 'p-instfolder', 'p-shortcut', 'p-export', 'p-copy',
|
||||
];
|
||||
|
||||
@@ -1575,6 +1610,7 @@ async function openEdit(inst) {
|
||||
}
|
||||
el('f-min-mem').value = inst.java.minMemMb || '';
|
||||
el('f-max-mem').value = inst.java.maxMemMb || '';
|
||||
zeigeSpeicherHinweis();
|
||||
el('f-extra-args').value = inst.java.extraArgs || '';
|
||||
el('f-prelaunch').value = inst.preLaunchCommand || '';
|
||||
el('f-postexit').value = inst.postExitCommand || '';
|
||||
@@ -1604,12 +1640,21 @@ async function saveInstance() {
|
||||
loaderVersion: el('f-loader-version').value.trim(),
|
||||
},
|
||||
};
|
||||
// Speicherangaben prüfen, bevor gespeichert wird
|
||||
const minMem = numOrNull(el('f-min-mem').value);
|
||||
const maxMem = numOrNull(el('f-max-mem').value);
|
||||
if (minMem || maxMem) {
|
||||
const pruefung = await window.api.checkMemory(minMem, maxMem);
|
||||
if (!pruefung.ok) { toast(pruefung.message, true); return; }
|
||||
if (pruefung.warnung) toast(pruefung.warnung, false);
|
||||
}
|
||||
|
||||
try {
|
||||
let targetId = editingId;
|
||||
if (editingId) {
|
||||
payload.java = {
|
||||
minMemMb: numOrNull(el('f-min-mem').value),
|
||||
maxMemMb: numOrNull(el('f-max-mem').value),
|
||||
minMemMb: minMem,
|
||||
maxMemMb: maxMem,
|
||||
extraArgs: el('f-extra-args').value.trim(),
|
||||
};
|
||||
payload.preLaunchCommand = el('f-prelaunch').value.trim();
|
||||
@@ -1666,22 +1711,348 @@ async function launch(id, accountId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Arbeitsspeicher ----------
|
||||
|
||||
let systemSpeicher = null;
|
||||
|
||||
async function zeigeSpeicherHinweis() {
|
||||
const feld = el('f-mem-hint');
|
||||
if (!feld) return;
|
||||
try {
|
||||
if (!systemSpeicher) systemSpeicher = await window.api.systemMemory();
|
||||
} catch { return; }
|
||||
const gesamt = systemSpeicher.totalMb;
|
||||
const empfohlen = Math.floor(gesamt * 0.5 / 256) * 256;
|
||||
feld.textContent = `Im Rechner stecken ${gesamt} MB. Leer lassen übernimmt die globale Vorgabe. `
|
||||
+ `Für die meisten Modpacks reichen 4096–8192 MB; mehr als ${Math.floor(gesamt * 0.8 / 256) * 256} MB sollten es nicht sein `
|
||||
+ `(Vorschlag: ${empfohlen} MB).`;
|
||||
}
|
||||
|
||||
// ---------- Serverliste ----------
|
||||
|
||||
let zuUebertragenderServer = null;
|
||||
|
||||
async function openServers() {
|
||||
if (!currentDetailId) return;
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
el('servers-title').textContent = 'Server – ' + (inst ? inst.name : '');
|
||||
el('srv-name').value = '';
|
||||
el('srv-ip').value = '';
|
||||
show('modal-servers');
|
||||
await renderServers();
|
||||
}
|
||||
|
||||
async function renderServers() {
|
||||
const box = el('srv-list');
|
||||
box.innerHTML = '<div class="mods-empty">Lade …</div>';
|
||||
const res = await window.api.serversList(currentDetailId);
|
||||
box.innerHTML = '';
|
||||
|
||||
if (!res.ok) { box.innerHTML = `<div class="mods-empty">${escapeHtml(res.message || 'Liste nicht lesbar.')}</div>`; return; }
|
||||
if (!res.server.length) {
|
||||
box.innerHTML = '<div class="mods-empty">Noch keine Server eingetragen.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const s of res.server) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'mod-item';
|
||||
item.innerHTML =
|
||||
`<div class="mod-icon">🖥</div>
|
||||
<div class="mod-info">
|
||||
<div class="mod-name">${escapeHtml(s.name || s.ip)}</div>
|
||||
<div class="mod-meta">${escapeHtml(s.ip)}</div>
|
||||
</div>
|
||||
<div class="mod-action"></div>`;
|
||||
const aktionen = item.querySelector('.mod-action');
|
||||
|
||||
const kopieren = document.createElement('button');
|
||||
kopieren.className = 'btn btn-mini';
|
||||
kopieren.textContent = 'Übertragen…';
|
||||
kopieren.title = 'Diesen Server in andere Instanzen eintragen';
|
||||
kopieren.addEventListener('click', () => openServerCopy(s));
|
||||
aktionen.appendChild(kopieren);
|
||||
|
||||
const weg = document.createElement('button');
|
||||
weg.className = 'btn btn-danger btn-mini';
|
||||
weg.textContent = 'Entfernen';
|
||||
weg.addEventListener('click', async () => {
|
||||
const r = await window.api.serversRemove(currentDetailId, s.ip);
|
||||
if (r.ok) toast('Entfernt: ' + (s.name || s.ip));
|
||||
else toast(r.message || 'Entfernen fehlgeschlagen.', true);
|
||||
await renderServers();
|
||||
});
|
||||
aktionen.appendChild(weg);
|
||||
box.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
async function addServer() {
|
||||
const eintrag = { name: el('srv-name').value.trim(), ip: el('srv-ip').value.trim() };
|
||||
const res = await window.api.serversAdd(currentDetailId, eintrag);
|
||||
if (!res.ok) { toast(res.message || 'Hinzufügen fehlgeschlagen.', true); return; }
|
||||
el('srv-name').value = '';
|
||||
el('srv-ip').value = '';
|
||||
toast('Server hinzugefügt.');
|
||||
await renderServers();
|
||||
}
|
||||
|
||||
async function openServerCopy(server) {
|
||||
zuUebertragenderServer = server;
|
||||
el('srv-copy-sub').textContent = `„${server.name || server.ip}" (${server.ip}) in diese Instanzen eintragen:`;
|
||||
const box = el('srv-copy-list');
|
||||
box.innerHTML = '';
|
||||
const alle = await window.api.listInstances();
|
||||
const andere = alle.filter((i) => i.id !== currentDetailId);
|
||||
if (!andere.length) {
|
||||
box.innerHTML = '<div class="mods-empty">Es gibt keine weitere Instanz.</div>';
|
||||
}
|
||||
for (const i of andere) {
|
||||
const zeile = document.createElement('label');
|
||||
zeile.className = 'checkbox-field';
|
||||
zeile.style.padding = '6px 2px';
|
||||
zeile.innerHTML = `<input type="checkbox" value="${escapeHtml(i.id)}" /> <span>${escapeHtml(i.name)}</span>`;
|
||||
box.appendChild(zeile);
|
||||
}
|
||||
show('modal-srv-copy');
|
||||
}
|
||||
|
||||
async function doServerCopy() {
|
||||
if (!zuUebertragenderServer) return;
|
||||
const ids = [...el('srv-copy-list').querySelectorAll('input:checked')].map((c) => c.value);
|
||||
if (!ids.length) { toast('Keine Instanz ausgewählt.', true); return; }
|
||||
|
||||
const res = await window.api.serversCopyTo(ids, zuUebertragenderServer);
|
||||
hide('modal-srv-copy');
|
||||
if (res.erledigt.length) toast(`Eingetragen in: ${res.erledigt.join(', ')}`);
|
||||
for (const f of res.fehler) toast(`${f.name || f.id}: ${f.message}`, true);
|
||||
}
|
||||
|
||||
// ---------- Skin ändern ----------
|
||||
|
||||
let gewaehlterSkin = null;
|
||||
|
||||
/*
|
||||
* Bild nur zeigen, wenn es wirklich geladen wurde – ein <img> ohne Quelle
|
||||
* stellt sonst das Platzhalter-Symbol des Browsers dar.
|
||||
*/
|
||||
function zeigeSkinBild(bildId, leerId, quelle, leerText) {
|
||||
const bild = el(bildId);
|
||||
const leer = el(leerId);
|
||||
leer.textContent = leerText;
|
||||
if (!quelle) {
|
||||
bild.classList.add('hidden');
|
||||
bild.removeAttribute('src');
|
||||
leer.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
bild.onload = () => { bild.classList.remove('hidden'); leer.classList.add('hidden'); };
|
||||
bild.onerror = () => {
|
||||
bild.classList.add('hidden');
|
||||
bild.removeAttribute('src');
|
||||
leer.textContent = 'Vorschau nicht ladbar';
|
||||
leer.classList.remove('hidden');
|
||||
};
|
||||
bild.src = quelle;
|
||||
}
|
||||
|
||||
// Textur einer Seite merken, damit bei einem Wechsel der Armform beide
|
||||
// Figuren neu gerendert werden können
|
||||
let skinTexturAktuell = '';
|
||||
let skinTexturNeu = '';
|
||||
|
||||
// Skin-Textur als Figur rendern; klappt das nicht, wird die Textur gezeigt
|
||||
async function zeigeSkinFigur(bildId, leerId, textur, leerText) {
|
||||
if (!textur) { zeigeSkinBild(bildId, leerId, '', leerText); return; }
|
||||
el(leerId).textContent = 'wird gerendert …';
|
||||
const modell = el('skin-variante').value === 'slim' ? 'slim' : 'default';
|
||||
const figur = await renderSkin3D(textur, 'standing', { modell, breite: 260, hoehe: 420 });
|
||||
zeigeSkinBild(bildId, leerId, figur || textur, leerText);
|
||||
}
|
||||
|
||||
async function openSkin() {
|
||||
gewaehlterSkin = null;
|
||||
skinTexturAktuell = '';
|
||||
skinTexturNeu = '';
|
||||
el('skin-pfad').value = '';
|
||||
el('skin-apply').disabled = true;
|
||||
el('skin-status').textContent = '';
|
||||
zeigeSkinBild('skin-neu', 'skin-neu-leer', '', 'noch keine Datei gewählt');
|
||||
zeigeSkinBild('skin-aktuell', 'skin-aktuell-leer', '', 'wird geladen …');
|
||||
el('skin-konto').textContent = 'Profil wird geladen …';
|
||||
show('modal-skin');
|
||||
|
||||
const p = await window.api.skinProfile();
|
||||
if (!p.ok) {
|
||||
el('skin-konto').textContent = p.message || 'Profil konnte nicht geladen werden.';
|
||||
zeigeSkinBild('skin-aktuell', 'skin-aktuell-leer', '', 'kein Profil geladen');
|
||||
return;
|
||||
}
|
||||
el('skin-konto').textContent = `Angemeldet als ${p.name}. Die Änderung gilt für dieses Konto.`;
|
||||
// Armform zuerst setzen – davon hängt ab, wie die Figur gerendert wird
|
||||
if (p.variante) el('skin-variante').value = p.variante === 'slim' ? 'slim' : 'classic';
|
||||
skinTexturAktuell = p.skinUrl || '';
|
||||
await zeigeSkinFigur('skin-aktuell', 'skin-aktuell-leer', skinTexturAktuell,
|
||||
'Standard-Skin (keine eigene Datei hinterlegt)');
|
||||
}
|
||||
|
||||
// Armform gewechselt -> beide Figuren neu zeichnen
|
||||
async function skinVarianteGewechselt() {
|
||||
if (skinTexturAktuell) await zeigeSkinFigur('skin-aktuell', 'skin-aktuell-leer', skinTexturAktuell, '');
|
||||
if (skinTexturNeu) await zeigeSkinFigur('skin-neu', 'skin-neu-leer', skinTexturNeu, '');
|
||||
}
|
||||
|
||||
async function waehleSkinDatei() {
|
||||
const res = await window.api.skinChoose();
|
||||
if (res.canceled) return;
|
||||
if (!res.ok) { el('skin-status').textContent = '✖ ' + res.message; return; }
|
||||
|
||||
gewaehlterSkin = res.pfad;
|
||||
el('skin-pfad').value = res.pfad;
|
||||
skinTexturNeu = res.dataUri || '';
|
||||
await zeigeSkinFigur('skin-neu', 'skin-neu-leer', skinTexturNeu, 'Vorschau nicht verfügbar');
|
||||
el('skin-status').textContent = `${res.breite}×${res.hoehe} Pixel – sieht gut aus.`;
|
||||
el('skin-apply').disabled = false;
|
||||
}
|
||||
|
||||
async function setzeSkin() {
|
||||
if (!gewaehlterSkin) return;
|
||||
const btn = el('skin-apply');
|
||||
btn.disabled = true;
|
||||
el('skin-status').textContent = 'Wird hochgeladen …';
|
||||
const res = await window.api.skinApply(gewaehlterSkin, el('skin-variante').value);
|
||||
if (res.ok) {
|
||||
el('skin-status').textContent = '✔ Skin gesetzt. Im Spiel kann es einen Moment dauern.';
|
||||
toast('Skin geändert.');
|
||||
await openSkin(); // Vorschau mit dem neuen Stand auffrischen
|
||||
await applySkinBackground(); // auch der Hintergrund zeigt jetzt den neuen Skin
|
||||
} else {
|
||||
el('skin-status').textContent = '✖ ' + (res.message || 'Fehlgeschlagen.');
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function setzeSkinZurueck() {
|
||||
const btn = el('skin-reset');
|
||||
btn.disabled = true;
|
||||
el('skin-status').textContent = 'Wird zurückgesetzt …';
|
||||
const res = await window.api.skinReset();
|
||||
if (res.ok) {
|
||||
toast('Skin auf Standard zurückgesetzt.');
|
||||
await openSkin();
|
||||
await applySkinBackground();
|
||||
} else {
|
||||
el('skin-status').textContent = '✖ ' + (res.message || 'Fehlgeschlagen.');
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// ---------- Logdateien ----------
|
||||
|
||||
let aktuellesLog = '';
|
||||
|
||||
async function openLogs() {
|
||||
if (!currentDetailId) return;
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
el('logs-title').textContent = 'Logdateien – ' + (inst ? inst.name : '');
|
||||
const auswahl = el('logs-select');
|
||||
auswahl.innerHTML = '';
|
||||
el('logs-text').textContent = 'Lade …';
|
||||
el('logs-info').textContent = '';
|
||||
aktuellesLog = '';
|
||||
show('modal-logs');
|
||||
|
||||
const dateien = await window.api.logsList(currentDetailId);
|
||||
if (!dateien.length) {
|
||||
el('logs-text').textContent = 'Für diese Instanz gibt es noch keine Logdateien. '
|
||||
+ 'Sie entstehen beim ersten Spielstart.';
|
||||
return;
|
||||
}
|
||||
for (const d of dateien) {
|
||||
const o = document.createElement('option');
|
||||
o.value = d.name;
|
||||
const kb = Math.max(1, Math.round(d.groesse / 1024));
|
||||
o.textContent = `${d.name} · ${kb} KB · ${new Date(d.geaendert).toLocaleString('de-DE')}`
|
||||
+ (d.gepackt ? ' · gepackt' : '');
|
||||
auswahl.appendChild(o);
|
||||
}
|
||||
await ladeLog(dateien[0].name);
|
||||
}
|
||||
|
||||
async function ladeLog(name) {
|
||||
el('logs-text').textContent = 'Lade …';
|
||||
const res = await window.api.logsRead(currentDetailId, name);
|
||||
if (!res.ok) {
|
||||
el('logs-text').textContent = 'Datei konnte nicht gelesen werden.';
|
||||
el('logs-info').textContent = '';
|
||||
aktuellesLog = '';
|
||||
return;
|
||||
}
|
||||
aktuellesLog = res.text;
|
||||
el('logs-text').textContent = res.text || '(leer)';
|
||||
el('logs-info').textContent = res.gekuerzt
|
||||
? `${res.zeilenGesamt} Zeilen – die letzten 5000 werden gezeigt`
|
||||
: `${res.zeilenGesamt} Zeilen`;
|
||||
// ans Ende springen: dort steht, was zuletzt passierte
|
||||
const feld = el('logs-text');
|
||||
feld.scrollTop = feld.scrollHeight;
|
||||
}
|
||||
|
||||
// ---------- Absturzberichte ----------
|
||||
|
||||
let letzterStartMs = 0;
|
||||
let aktuellerCrash = null;
|
||||
|
||||
async function zeigeAbsturzbericht(instanzId) {
|
||||
const id = instanzId || currentDetailId;
|
||||
if (!id) return;
|
||||
// Minecraft schreibt den Bericht erst kurz nach dem Ende
|
||||
await new Promise((r) => setTimeout(r, 900));
|
||||
|
||||
let res;
|
||||
try { res = await window.api.crashLatest(id, letzterStartMs); }
|
||||
catch { return; }
|
||||
if (!res || !res.ok) return; // kein Bericht -> Konsole muss reichen
|
||||
|
||||
aktuellerCrash = res;
|
||||
const inst = await window.api.getInstance(id);
|
||||
el('crash-sub').textContent = `${inst ? inst.name + ' · ' : ''}${res.datei} · `
|
||||
+ new Date(res.zeitpunkt).toLocaleString('de-DE');
|
||||
|
||||
const zeile = (titel, wert) => wert
|
||||
? `<div class="crash-zeile"><div class="crash-titel">${titel}</div><div class="crash-wert">${escapeHtml(wert)}</div></div>`
|
||||
: '';
|
||||
let html = zeile('Beschreibung', res.beschreibung) + zeile('Fehler', res.fehler)
|
||||
+ zeile('Verdächtige Mods', res.verdaechtigeMods);
|
||||
if (res.verlauf && res.verlauf.length) {
|
||||
html += `<div class="crash-zeile"><div class="crash-titel">Aufrufverlauf</div>`
|
||||
+ `<div class="crash-wert"><div class="crash-verlauf">${escapeHtml(res.verlauf.join('\n'))}</div></div></div>`;
|
||||
}
|
||||
el('crash-kern').innerHTML = html || '<div class="hint">Der Bericht enthält keine der üblichen Kennzeichen.</div>';
|
||||
el('crash-voll').textContent = res.text || '';
|
||||
show('modal-crash');
|
||||
}
|
||||
|
||||
// Fortschrittsmeldungen der Download-/Start-Pipeline
|
||||
function onLaunchEvt(evt) {
|
||||
const labels = { version: 'Version', client: 'Client', libraries: 'Libraries', assets: 'Assets', loader: 'Loader', java: 'Java', launch: 'Start', modpack: 'Modpack' };
|
||||
const labels = { version: 'Version', client: 'Client', libraries: 'Libraries', assets: 'Assets', loader: 'Loader', java: 'Java', launch: 'Start', modpack: 'Modpack', backup: 'Sicherung' };
|
||||
if (evt.phase === 'log') { appendConsole(evt.line); return; }
|
||||
if (evt.phase === 'exit') {
|
||||
status('Minecraft beendet (Code ' + evt.code + ')');
|
||||
el('status-right').textContent = '';
|
||||
appendConsole('\n[Prozess beendet, Code ' + evt.code + ']\n');
|
||||
// Konsole je nach Einstellung schließen bzw. bei Absturz zeigen
|
||||
const crashed = evt.code !== 0 && evt.code !== null;
|
||||
// Absturzbericht suchen, bevor über die Konsole entschieden wird –
|
||||
// ein Bericht sagt mehr als der Java-Auswurf
|
||||
if (crashed) zeigeAbsturzbericht(evt.id);
|
||||
window.api.getSettings().then((s) => {
|
||||
const crashed = evt.code !== 0 && evt.code !== null;
|
||||
if (crashed && s.showConsoleOnCrash !== false) { openConsole(); return; }
|
||||
if (s.autoCloseConsole) hide('modal-console');
|
||||
}).catch(() => {});
|
||||
return;
|
||||
}
|
||||
// Startzeitpunkt merken: nur Berichte ab hier gehören zu diesem Lauf
|
||||
if (evt.phase === 'launch') letzterStartMs = Date.now();
|
||||
const label = labels[evt.phase] || evt.phase;
|
||||
if (evt.total) {
|
||||
status(`${label} … ${evt.current}/${evt.total}`);
|
||||
@@ -1815,6 +2186,11 @@ async function openMods() {
|
||||
['m-query', 'm-search-btn', 'm-sort', 'm-category'].forEach((id) => { el(id).disabled = blocked; });
|
||||
el('m-query').value = '';
|
||||
el('m-more').classList.add('hidden');
|
||||
// Ergebnisse der vorigen Instanz nicht mitschleppen
|
||||
modUpdates = new Map();
|
||||
setUpdateStatus('');
|
||||
el('m-apply-updates').classList.add('hidden');
|
||||
el('m-check-updates').disabled = blocked;
|
||||
switchModTab('catalog');
|
||||
|
||||
await refreshInstalled();
|
||||
@@ -1846,6 +2222,9 @@ function switchModTab(which) {
|
||||
el('m-pane-installed').classList.toggle('hidden', cat);
|
||||
}
|
||||
|
||||
// gefundene Aktualisierungen: projectId -> { title, alt, neu }
|
||||
let modUpdates = new Map();
|
||||
|
||||
async function refreshInstalled() {
|
||||
const list = await window.api.modList(currentDetailId);
|
||||
currentModsInstalled = new Set(list.map((m) => m.projectId));
|
||||
@@ -1854,15 +2233,46 @@ async function refreshInstalled() {
|
||||
box.innerHTML = '';
|
||||
if (!list.length) { box.innerHTML = '<div class="mods-empty">Noch keine Mods.</div>'; return; }
|
||||
for (const m of list) {
|
||||
const up = m.projectId ? modUpdates.get(m.projectId) : null;
|
||||
const aus = m.enabled === false;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'mod-item';
|
||||
item.className = 'mod-item' + (aus ? ' mod-aus' : '');
|
||||
|
||||
const kennzeichen = [];
|
||||
if (up) kennzeichen.push('<span class="mod-new">Update</span>');
|
||||
if (aus) kennzeichen.push('<span class="mod-off">aus</span>');
|
||||
if (m.fehlt) kennzeichen.push('<span class="mod-off">Datei fehlt</span>');
|
||||
|
||||
item.innerHTML =
|
||||
`<div class="mod-icon">🧩</div>
|
||||
`<div class="mod-icon">${aus ? '🚫' : '🧩'}</div>
|
||||
<div class="mod-info">
|
||||
<div class="mod-name">${escapeHtml(m.title)}</div>
|
||||
<div class="mod-meta">${escapeHtml(m.versionNumber || '')} · ${escapeHtml(m.filename)}</div>
|
||||
<div class="mod-name">${escapeHtml(m.title)}${kennzeichen.length ? ' ' + kennzeichen.join(' ') : ''}</div>
|
||||
<div class="mod-meta">${up
|
||||
? escapeHtml(up.alt.versionNumber) + ' → ' + escapeHtml(up.neu.versionNumber)
|
||||
: escapeHtml(m.versionNumber || '') + ' · ' + escapeHtml(m.filename)}</div>
|
||||
</div>
|
||||
<div class="mod-action"></div>`;
|
||||
|
||||
const aktionen = item.querySelector('.mod-action');
|
||||
|
||||
// Ab-/Anschalten: die Datei bleibt liegen, Minecraft übergeht sie nur
|
||||
if (!m.fehlt) {
|
||||
const schalter = document.createElement('button');
|
||||
schalter.className = 'btn btn-mini';
|
||||
schalter.textContent = aus ? 'Einschalten' : 'Ausschalten';
|
||||
schalter.title = aus
|
||||
? 'Datei wieder auf .jar umbenennen'
|
||||
: 'Datei auf .jar.disabled umbenennen – Minecraft lädt sie dann nicht';
|
||||
schalter.addEventListener('click', async () => {
|
||||
schalter.disabled = true;
|
||||
const res = await window.api.modSetEnabled(currentDetailId, m.filename, aus);
|
||||
if (res.ok) toast((aus ? 'Eingeschaltet: ' : 'Ausgeschaltet: ') + m.title);
|
||||
else toast(res.message || 'Umschalten fehlgeschlagen.', true);
|
||||
await refreshInstalled();
|
||||
});
|
||||
aktionen.appendChild(schalter);
|
||||
}
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-danger btn-mini';
|
||||
btn.textContent = 'Entfernen';
|
||||
@@ -1871,11 +2281,61 @@ async function refreshInstalled() {
|
||||
toast('Entfernt: ' + m.title);
|
||||
await refreshInstalled();
|
||||
});
|
||||
item.querySelector('.mod-action').appendChild(btn);
|
||||
aktionen.appendChild(btn);
|
||||
box.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Mod-Aktualisierung ----------
|
||||
|
||||
function setUpdateStatus(text) { el('m-update-status').textContent = text || ''; }
|
||||
|
||||
async function checkModUpdates() {
|
||||
const btn = el('m-check-updates');
|
||||
btn.disabled = true;
|
||||
el('m-apply-updates').classList.add('hidden');
|
||||
setUpdateStatus('Suche …');
|
||||
try {
|
||||
const res = await window.api.modCheckUpdates(currentDetailId);
|
||||
if (!res.ok) { setUpdateStatus('✖ ' + (res.message || 'Suche fehlgeschlagen.')); return; }
|
||||
|
||||
modUpdates = new Map((res.treffer || []).map((t) => [t.projectId, t]));
|
||||
await refreshInstalled();
|
||||
|
||||
const teile = [];
|
||||
if (!res.treffer.length) teile.push('Alles aktuell');
|
||||
else teile.push(res.treffer.length + (res.treffer.length === 1 ? ' Update verfügbar' : ' Updates verfügbar'));
|
||||
teile.push(res.geprueft + ' geprüft');
|
||||
// von Hand abgelegte Dateien lassen sich nicht zuordnen
|
||||
if (res.uebersprungen) teile.push(res.uebersprungen + ' ohne Quelle übersprungen');
|
||||
setUpdateStatus(teile.join(' · '));
|
||||
el('m-apply-updates').classList.toggle('hidden', !res.treffer.length);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyModUpdates() {
|
||||
if (!modUpdates.size) return;
|
||||
const btn = el('m-apply-updates');
|
||||
btn.disabled = true;
|
||||
setUpdateStatus('Wird aktualisiert …');
|
||||
try {
|
||||
const res = await window.api.modApplyUpdates(currentDetailId, [...modUpdates.keys()]);
|
||||
if (!res.ok) { setUpdateStatus('✖ ' + (res.message || 'Aktualisierung fehlgeschlagen.')); return; }
|
||||
modUpdates = new Map();
|
||||
await refreshInstalled();
|
||||
const n = res.erneuert.length;
|
||||
setUpdateStatus(n + (n === 1 ? ' Mod aktualisiert' : ' Mods aktualisiert')
|
||||
+ (res.fehler.length ? ' · ' + res.fehler.length + ' fehlgeschlagen' : ''));
|
||||
if (n) toast(n + (n === 1 ? ' Mod aktualisiert.' : ' Mods aktualisiert.'));
|
||||
for (const f of res.fehler) toast(f.title + ': ' + f.message, true);
|
||||
btn.classList.add('hidden');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
let modOffset = 0;
|
||||
async function loadCatalog(append) {
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
@@ -1973,8 +2433,9 @@ function setSettingsPane(paneId) {
|
||||
document.querySelectorAll('.spane').forEach((pane) => pane.classList.toggle('hidden', pane.id !== paneId));
|
||||
}
|
||||
|
||||
async function openSettings() {
|
||||
setSettingsPane('pane-general');
|
||||
// pane: optionaler Bereich, der direkt geöffnet werden soll (z. B. 'pane-network')
|
||||
async function openSettings(pane) {
|
||||
setSettingsPane(pane || 'pane-general');
|
||||
|
||||
const s = await window.api.getSettings();
|
||||
el('s-theme').value = s.theme || 'dark';
|
||||
@@ -1996,6 +2457,14 @@ async function openSettings() {
|
||||
el('s-sortby').value = s.sortBy || 'lastPlayed';
|
||||
el('s-iso3d').checked = s.iso3dIcons !== false;
|
||||
el('s-bedrock').checked = s.bedrockEnabled !== false;
|
||||
el('s-auto-java').checked = s.autoJava !== false;
|
||||
el('s-autobackup').checked = !!s.autoBackupEnabled;
|
||||
el('s-autobackup-every').value = s.autoBackupEvery || 'daily';
|
||||
el('s-autobackup-keep').value = s.autoBackupKeep || 5;
|
||||
el('s-autobackup-launch').checked = !!s.autoBackupBeforeLaunch;
|
||||
el('s-autobackup-last').textContent = s.autoBackupLast
|
||||
? 'Zuletzt automatisch gesichert: ' + new Date(s.autoBackupLast).toLocaleString('de-DE')
|
||||
: 'Noch keine automatische Sicherung.';
|
||||
el('s-console-launch').checked = !!s.showConsoleOnLaunch;
|
||||
el('s-console-autoclose').checked = !!s.autoCloseConsole;
|
||||
el('s-console-crash').checked = s.showConsoleOnCrash !== false;
|
||||
@@ -2010,6 +2479,9 @@ async function openSettings() {
|
||||
el('s-proxy-port').value = s.proxyPort || '';
|
||||
el('s-proxy-user').value = s.proxyUser || '';
|
||||
el('s-proxy-pass').value = s.proxyPass || '';
|
||||
el('s-cf-key').value = s.curseforgeApiKey || '';
|
||||
el('s-cf-proxy').value = s.curseforgeProxyUrl || '';
|
||||
el('s-cf-proxy-token').value = s.curseforgeProxyToken || '';
|
||||
el('s-backup-status').className = 'hint';
|
||||
el('s-backup-status').textContent = 'Noch kein Backup erstellt.';
|
||||
el('s-backup-global').disabled = false;
|
||||
@@ -2086,6 +2558,13 @@ async function scanJava() {
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
// globale Speicher-Vorgaben genauso prüfen wie die je Instanz
|
||||
const gMin = numOrNull(el('s-min-mem').value) || 2048;
|
||||
const gMax = numOrNull(el('s-max-mem').value) || 4096;
|
||||
const pruefung = await window.api.checkMemory(gMin, gMax);
|
||||
if (!pruefung.ok) { toast(pruefung.message, true); setSettingsPane('pane-minecraft'); return; }
|
||||
if (pruefung.warnung) toast(pruefung.warnung, false);
|
||||
|
||||
const patch = {
|
||||
theme: el('s-theme').value,
|
||||
globalPreLaunch: el('s-global-pre').value.trim(),
|
||||
@@ -2095,6 +2574,11 @@ async function saveSettings() {
|
||||
sortBy: el('s-sortby').value,
|
||||
iso3dIcons: el('s-iso3d').checked,
|
||||
bedrockEnabled: el('s-bedrock').checked,
|
||||
autoJava: el('s-auto-java').checked,
|
||||
autoBackupEnabled: el('s-autobackup').checked,
|
||||
autoBackupEvery: el('s-autobackup-every').value,
|
||||
autoBackupKeep: Math.min(50, Math.max(1, numOrNull(el('s-autobackup-keep').value) || 5)),
|
||||
autoBackupBeforeLaunch: el('s-autobackup-launch').checked,
|
||||
showConsoleOnLaunch: el('s-console-launch').checked,
|
||||
autoCloseConsole: el('s-console-autoclose').checked,
|
||||
showConsoleOnCrash: el('s-console-crash').checked,
|
||||
@@ -2109,6 +2593,9 @@ async function saveSettings() {
|
||||
proxyPort: el('s-proxy-port').value.trim(),
|
||||
proxyUser: el('s-proxy-user').value.trim(),
|
||||
proxyPass: el('s-proxy-pass').value,
|
||||
curseforgeApiKey: el('s-cf-key').value.trim(),
|
||||
curseforgeProxyUrl: el('s-cf-proxy').value.trim(),
|
||||
curseforgeProxyToken: el('s-cf-proxy-token').value.trim(),
|
||||
javaPath: el('s-java').value.trim(),
|
||||
defaultMinMemMb: numOrNull(el('s-min-mem').value) || 2048,
|
||||
defaultMaxMemMb: numOrNull(el('s-max-mem').value) || 4096,
|
||||
@@ -2421,11 +2908,16 @@ async function renderAccounts(boxId = 'login-accounts') {
|
||||
av.replaceWith(d);
|
||||
});
|
||||
|
||||
// "spielt" nur, wenn wirklich ein Spiel dieses Kontos läuft; "aktiv" heißt
|
||||
// lediglich: dieses Konto wird für den nächsten Start verwendet
|
||||
const merkmale = [];
|
||||
if (a.active) merkmale.push('aktiv');
|
||||
if (a.playing) merkmale.push('spielt gerade');
|
||||
const main = document.createElement('div');
|
||||
main.className = 'account-main';
|
||||
main.innerHTML =
|
||||
`<div class="account-name">${escapeHtml(a.name)}</div>
|
||||
<div class="account-tag">${a.active ? '● aktiv (spielt)' : ''}</div>`;
|
||||
<div class="account-tag">${merkmale.length ? '● ' + merkmale.join(' · ') : ''}</div>`;
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'account-actions';
|
||||
@@ -2457,10 +2949,16 @@ async function renderAccounts(boxId = 'login-accounts') {
|
||||
}
|
||||
}
|
||||
|
||||
// steht wortgleich in index.html – von dort wird er beim Öffnen zurückgesetzt
|
||||
const LOGIN_HINWEIS = 'Melde dich mit deinem Microsoft-Konto an (Minecraft Java Edition nötig). '
|
||||
+ 'Das aktive Konto wird beim nächsten Start verwendet. Für zwei Konten gleichzeitig: '
|
||||
+ 'eine Instanz starten, hier das zweite Konto aktivieren und eine weitere Instanz starten – '
|
||||
+ 'laufende Spiele behalten ihr Konto.';
|
||||
|
||||
function resetLoginHint() {
|
||||
const h = el('login-hint');
|
||||
h.classList.remove('login-error');
|
||||
h.textContent = 'Melde dich mit deinem Microsoft-Konto an (Minecraft Java Edition nötig). Mehrere Konten sind möglich – das aktive wird zum Spielen verwendet.';
|
||||
h.textContent = LOGIN_HINWEIS;
|
||||
}
|
||||
|
||||
async function openLogin() {
|
||||
@@ -2517,7 +3015,8 @@ function askConfirm(title, text, okLabel, action) {
|
||||
function wire() {
|
||||
el('btn-new').addEventListener('click', openCreate);
|
||||
el('btn-new-2').addEventListener('click', openCreate);
|
||||
el('btn-settings').addEventListener('click', openSettings);
|
||||
// ohne Wrapper käme das Klick-Ereignis als Bereichsname an
|
||||
el('btn-settings').addEventListener('click', () => openSettings());
|
||||
el('search').addEventListener('input', render);
|
||||
|
||||
el('btn-open-dir').addEventListener('click', () => {
|
||||
@@ -2674,13 +3173,38 @@ function wire() {
|
||||
// Rechte Aktionsleiste (Instanz-Aktionen)
|
||||
el('p-play').addEventListener('click', () => currentDetailId && launch(currentDetailId));
|
||||
el('p-offline').addEventListener('click', () => currentDetailId && launch(currentDetailId));
|
||||
el('p-shortcut').addEventListener('click', () => toast('Verknüpfung erstellen folgt in einem Update.', false));
|
||||
el('p-shortcut').addEventListener('click', async () => {
|
||||
if (!currentDetailId) return;
|
||||
const res = await window.api.createShortcut(currentDetailId);
|
||||
if (res.ok) toast('Verknüpfung auf dem Desktop erstellt.');
|
||||
else toast(res.message || 'Verknüpfung fehlgeschlagen.', true);
|
||||
});
|
||||
el('p-edit').addEventListener('click', async () => { if (!currentDetailId) return; const inst = await window.api.getInstance(currentDetailId); if (inst) openEdit(inst); });
|
||||
el('p-notes').addEventListener('click', async () => { if (!currentDetailId) return; const inst = await window.api.getInstance(currentDetailId); if (inst) openEdit(inst); });
|
||||
el('p-groupline').addEventListener('click', async () => { if (!currentDetailId) return; const inst = await window.api.getInstance(currentDetailId); if (inst) openEdit(inst); });
|
||||
el('p-mods').addEventListener('click', openMods);
|
||||
el('p-worlds').addEventListener('click', openContent);
|
||||
el('p-screens').addEventListener('click', openScreens);
|
||||
el('p-logs').addEventListener('click', openLogs);
|
||||
el('p-servers').addEventListener('click', openServers);
|
||||
el('srv-add').addEventListener('click', addServer);
|
||||
el('srv-ip').addEventListener('keydown', (e) => { if (e.key === 'Enter') addServer(); });
|
||||
el('srv-copy-do').addEventListener('click', doServerCopy);
|
||||
el('login-skin').addEventListener('click', openSkin);
|
||||
el('skin-pick').addEventListener('click', waehleSkinDatei);
|
||||
el('skin-variante').addEventListener('change', skinVarianteGewechselt);
|
||||
el('skin-apply').addEventListener('click', setzeSkin);
|
||||
el('skin-reset').addEventListener('click', setzeSkinZurueck);
|
||||
el('logs-select').addEventListener('change', (e) => ladeLog(e.target.value));
|
||||
el('logs-folder').addEventListener('click', async () => {
|
||||
const ok = await window.api.logsOpenFolder(currentDetailId);
|
||||
if (!ok) toast('Log-Ordner nicht gefunden.', true);
|
||||
});
|
||||
el('logs-copy').addEventListener('click', async () => {
|
||||
if (!aktuellesLog) return;
|
||||
try { await navigator.clipboard.writeText(aktuellesLog); toast('Log kopiert.'); }
|
||||
catch { toast('Kopieren fehlgeschlagen.', true); }
|
||||
});
|
||||
el('p-mcfolder').addEventListener('click', () => currentDetailId && window.api.openGameDir(currentDetailId));
|
||||
el('p-configfolder').addEventListener('click', () => currentDetailId && window.api.openSubdir(currentDetailId, 'config'));
|
||||
el('p-instfolder').addEventListener('click', () => currentDetailId && window.api.openInstanceFolder(currentDetailId));
|
||||
@@ -2743,6 +3267,27 @@ function wire() {
|
||||
el('m-tab-catalog').addEventListener('click', () => switchModTab('catalog'));
|
||||
el('m-tab-installed').addEventListener('click', () => switchModTab('installed'));
|
||||
el('m-open-folder').addEventListener('click', () => window.api.openGameDir(currentDetailId));
|
||||
// Automatische Sicherung: unaufdringlich melden, Fehler deutlich
|
||||
window.api.onAutoBackup((d) => {
|
||||
if (d.ok) toast(d.anlass === 'vor-dem-start' ? 'Sicherung vor dem Start erstellt.' : 'Automatische Sicherung erstellt.');
|
||||
else toast('Automatische Sicherung fehlgeschlagen: ' + (d.message || 'unbekannter Grund'), true);
|
||||
});
|
||||
|
||||
// Absturzbericht
|
||||
el('crash-copy').addEventListener('click', async () => {
|
||||
if (!aktuellerCrash) return;
|
||||
try { await navigator.clipboard.writeText(aktuellerCrash.text || ''); toast('Bericht kopiert.'); }
|
||||
catch { toast('Kopieren fehlgeschlagen.', true); }
|
||||
});
|
||||
el('crash-folder').addEventListener('click', async () => {
|
||||
const ok = await window.api.crashOpenFolder(currentDetailId);
|
||||
if (!ok) toast('Ordner mit den Berichten nicht gefunden.', true);
|
||||
});
|
||||
el('crash-console').addEventListener('click', () => { hide('modal-crash'); openConsole(); });
|
||||
|
||||
el('m-check-updates').addEventListener('click', checkModUpdates);
|
||||
el('m-apply-updates').addEventListener('click', applyModUpdates);
|
||||
window.api.onModUpdateProgress((p) => setUpdateStatus(`Suche … ${p.geprueft}/${p.gesamt} (${p.titel})`));
|
||||
|
||||
// Einstellungen
|
||||
el('s-pick-dir').addEventListener('click', async () => {
|
||||
@@ -2782,6 +3327,7 @@ function wire() {
|
||||
|
||||
// Updates
|
||||
el('upd-install').addEventListener('click', startUpdate);
|
||||
el('upd-open').addEventListener('click', openReleasePage);
|
||||
if (el('s-check-update')) {
|
||||
el('s-check-update').addEventListener('click', async () => {
|
||||
const st = el('s-update-status');
|
||||
@@ -2846,6 +3392,13 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
window.api.onAuthCode(onAuthCode);
|
||||
window.api.onAuthRefreshed(async () => { await refreshAccountButton(); await applySkinBackground(); });
|
||||
window.api.onAuthHealth(showAuthHealth);
|
||||
// Spiel gestartet oder beendet -> "spielt gerade" in den Kontenlisten nachziehen
|
||||
window.api.onAccountsChanged(() => { refreshAllAccountLists(); });
|
||||
// über eine Desktop-Verknüpfung gestartet -> Instanz auswählen und anwerfen
|
||||
window.api.onLaunchRequest(async (id) => {
|
||||
await selectInstance(id);
|
||||
launch(id);
|
||||
});
|
||||
window.api.onUpdateAvailable(showUpdateDialog);
|
||||
window.api.onUpdateProgress(onUpdateProgress);
|
||||
try { showAuthHealth(await window.api.authHealth()); } catch { /* noch keine Prüfung */ }
|
||||
|
||||
Reference in New Issue
Block a user