Upload via GUI (43 Dateien)
This commit is contained in:
+446
-33
@@ -470,6 +470,17 @@ const LOADER_LABEL = {
|
||||
neoforge: 'NeoForge', quilt: 'Quilt',
|
||||
};
|
||||
|
||||
// Bewährte Garbage-Collector-Flags (bekannt als "Aikar's Flags"), reduzieren
|
||||
// bei größeren Modpacks spürbar GC-bedingte Ruckler. Wirkt erst ab genug RAM
|
||||
// sinnvoll, schadet aber auch bei wenig RAM nicht.
|
||||
const AIKAR_FLAGS = '-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 '
|
||||
+ '-XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch '
|
||||
+ '-XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M '
|
||||
+ '-XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 '
|
||||
+ '-XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 '
|
||||
+ '-XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem '
|
||||
+ '-XX:MaxTenuringThreshold=1';
|
||||
|
||||
// ---------- Zustand ----------
|
||||
let instances = [];
|
||||
let currentDetailId = null;
|
||||
@@ -528,6 +539,7 @@ function applyTheme(theme) {
|
||||
let mmcFound = [];
|
||||
let cfFound = [];
|
||||
let mcFound = [];
|
||||
let technicFound = [];
|
||||
let importKind = 'zip';
|
||||
|
||||
async function ensureDefaultMcScan() {
|
||||
@@ -538,8 +550,8 @@ async function ensureDefaultMcScan() {
|
||||
}
|
||||
|
||||
function setImportKind(kind) {
|
||||
importKind = ['zip', 'modpack', 'mc', 'cf', 'mmc'].includes(kind) ? kind : 'zip';
|
||||
['zip', 'modpack', 'mc', 'cf', 'mmc'].forEach((entryKind) => {
|
||||
importKind = ['zip', 'modpack', 'mc', 'cf', 'mmc', 'technic'].includes(kind) ? kind : 'zip';
|
||||
['zip', 'modpack', 'mc', 'cf', 'mmc', 'technic'].forEach((entryKind) => {
|
||||
el(`import-tab-${entryKind}`).classList.toggle('on', entryKind === importKind);
|
||||
el(`import-pane-${entryKind}`).classList.toggle('hidden', entryKind !== importKind);
|
||||
});
|
||||
@@ -548,9 +560,11 @@ function setImportKind(kind) {
|
||||
el('mc-do').classList.toggle('hidden', importKind !== 'mc');
|
||||
el('cf-do').classList.toggle('hidden', importKind !== 'cf');
|
||||
el('mmc-do').classList.toggle('hidden', importKind !== 'mmc');
|
||||
el('technic-do').classList.toggle('hidden', importKind !== 'technic');
|
||||
el('mc-progress').classList.toggle('hidden', importKind !== 'mc' || !el('mc-progress').textContent);
|
||||
el('cf-progress').classList.toggle('hidden', importKind !== 'cf' || !el('cf-progress').textContent);
|
||||
el('mmc-progress').classList.toggle('hidden', importKind !== 'mmc' || !el('mmc-progress').textContent);
|
||||
el('technic-progress').classList.toggle('hidden', importKind !== 'technic' || !el('technic-progress').textContent);
|
||||
}
|
||||
|
||||
function openImportDialog(kind = 'zip') {
|
||||
@@ -923,6 +937,132 @@ async function runMmcImport() {
|
||||
if (instances.length) await selectInstance(instances[0].id);
|
||||
}
|
||||
|
||||
// ---------- Technic-Plattform: Modpack-Katalog ----------
|
||||
|
||||
async function searchTechnicPlatform() {
|
||||
const query = el('tp-query').value.trim();
|
||||
const box = el('tp-results');
|
||||
const st = el('tp-status');
|
||||
st.textContent = 'Suche …';
|
||||
box.innerHTML = '<div class="mods-empty">Suche läuft …</div>';
|
||||
const res = await window.api.technicPlatformSearch(query);
|
||||
if (!res.ok) {
|
||||
st.textContent = 'Fehler: ' + (res.message || 'Suche fehlgeschlagen.');
|
||||
box.innerHTML = '<div class="mods-empty">Suche fehlgeschlagen.</div>';
|
||||
return;
|
||||
}
|
||||
const hits = res.hits || [];
|
||||
st.textContent = hits.length ? `${hits.length} Treffer` : 'Keine Treffer.';
|
||||
box.innerHTML = '';
|
||||
if (!hits.length) { box.innerHTML = '<div class="mods-empty">Keine Modpacks gefunden.</div>'; return; }
|
||||
for (const h of hits) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'mod-item';
|
||||
item.innerHTML =
|
||||
`<div class="mod-icon">${h.iconUrl ? `<img src="${escapeHtml(h.iconUrl)}" alt="" style="width:32px;height:32px;border-radius:4px;object-fit:cover" />` : '📦'}</div>
|
||||
<div class="mod-info"><div class="mod-name">${escapeHtml(h.name)}</div></div>
|
||||
<div class="mod-action"></div>`;
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-primary btn-mini';
|
||||
btn.textContent = 'Installieren';
|
||||
btn.addEventListener('click', () => installTechnicFromSearch(h, btn));
|
||||
item.querySelector('.mod-action').appendChild(btn);
|
||||
box.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
async function installTechnicFromSearch(hit, btn) {
|
||||
if (!hit || !hit.slug) return;
|
||||
const prev = btn ? btn.textContent : '';
|
||||
if (btn) { btn.disabled = true; btn.textContent = '…'; }
|
||||
const st = el('tp-status');
|
||||
st.textContent = `Installiere „${hit.name}" …`;
|
||||
try {
|
||||
const res = await window.api.technicPlatformInstall(hit.slug);
|
||||
if (res.ok) {
|
||||
await reload();
|
||||
hide('modal-import');
|
||||
toast(`Modpack installiert: ${res.instance.name}`);
|
||||
await selectInstance(res.instance.id);
|
||||
} else {
|
||||
st.textContent = '✖ ' + (res.message || 'Installation fehlgeschlagen.');
|
||||
toast(res.message || 'Installation fehlgeschlagen.', true);
|
||||
}
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = prev; }
|
||||
}
|
||||
}
|
||||
|
||||
async function scanTechnic() {
|
||||
const folder = el('technic-path').value.trim();
|
||||
el('technic-status').textContent = 'Suche Modpacks …';
|
||||
const res = await window.api.technicScan(folder);
|
||||
if (!res.ok) {
|
||||
el('technic-status').textContent = '✖ Dort wurde kein modpacks-Ordner gefunden.';
|
||||
el('technic-result').classList.add('hidden');
|
||||
el('technic-do').disabled = true;
|
||||
return;
|
||||
}
|
||||
technicFound = res.instances;
|
||||
if (!folder && res.instancesDir) el('technic-path').value = res.instancesDir;
|
||||
el('technic-status').textContent = `${res.count} Modpack${res.count === 1 ? '' : 's'} gefunden.`;
|
||||
el('technic-result').classList.remove('hidden');
|
||||
el('technic-all').checked = true;
|
||||
renderTechnicList();
|
||||
}
|
||||
|
||||
function renderTechnicList() {
|
||||
const box = el('technic-list');
|
||||
box.innerHTML = '';
|
||||
if (!technicFound.length) { box.innerHTML = '<div class="mods-empty">Keine Modpacks gefunden.</div>'; return; }
|
||||
technicFound.forEach((inst, idx) => {
|
||||
const row = document.createElement('label');
|
||||
row.className = 'mmc-item';
|
||||
const loaderTxt = inst.loader === 'vanilla'
|
||||
? 'Vanilla'
|
||||
: (LOADER_LABEL[inst.loader] || inst.loader) + (inst.loaderVersion ? ' ' + inst.loaderVersion : '');
|
||||
const meta = [inst.version || 'Version unbekannt', loaderTxt]
|
||||
.filter(Boolean).join(' · ');
|
||||
row.innerHTML =
|
||||
`<input type="checkbox" data-idx="${idx}" checked />
|
||||
<div class="mod-info">
|
||||
<div class="mmc-name">${escapeHtml(inst.name)}</div>
|
||||
<div class="mmc-meta">${escapeHtml(meta)}</div>
|
||||
</div>`;
|
||||
box.appendChild(row);
|
||||
});
|
||||
updateTechnicButton();
|
||||
}
|
||||
|
||||
function selectedTechnic() {
|
||||
return [...document.querySelectorAll('#technic-list input[type=checkbox]')]
|
||||
.filter((c) => c.checked)
|
||||
.map((c) => technicFound[Number(c.dataset.idx)]);
|
||||
}
|
||||
|
||||
function updateTechnicButton() {
|
||||
const n = selectedTechnic().length;
|
||||
el('technic-do').disabled = n === 0;
|
||||
el('technic-do').textContent = n ? `${n} übernehmen` : 'Übernehmen';
|
||||
}
|
||||
|
||||
async function runTechnicImport() {
|
||||
const entries = selectedTechnic();
|
||||
if (!entries.length) return;
|
||||
const copy = el('technic-copy').checked;
|
||||
el('technic-do').disabled = true;
|
||||
el('technic-progress').textContent = 'Übernehme …';
|
||||
el('technic-progress').classList.remove('hidden');
|
||||
|
||||
const res = await window.api.technicImport(entries, copy);
|
||||
await reload();
|
||||
hide('modal-import');
|
||||
const n = res.imported.length;
|
||||
const f = res.failed.length;
|
||||
toast(`${n} Technic-Modpack${n === 1 ? '' : 's'} übernommen${f ? ` · ${f} fehlgeschlagen` : ''}.`, f > 0);
|
||||
if (instances.length) await selectInstance(instances[0].id);
|
||||
}
|
||||
|
||||
// ---------- Bedrock (Windows-Store-Ausgabe) ----------
|
||||
let bedrockKind = 'worlds';
|
||||
|
||||
@@ -1121,6 +1261,27 @@ function onUpdateProgress(p) {
|
||||
}
|
||||
}
|
||||
|
||||
// Aktualisiert die Statuszeile im Info-Bereich – genutzt sowohl vom manuellen
|
||||
// "Nach Updates suchen"-Knopf als auch vom automatischen Check beim Start.
|
||||
function renderUpdateStatus(info) {
|
||||
const st = el('s-update-status');
|
||||
if (!st) return;
|
||||
st.classList.remove('status-ok', 'status-bad');
|
||||
if (info.checking) {
|
||||
st.textContent = 'Suche nach Updates …';
|
||||
} else if (info.error) {
|
||||
st.classList.add('status-bad');
|
||||
st.textContent = '✖ ' + info.error;
|
||||
} else if (info.noReleases) {
|
||||
st.textContent = 'Noch keine Veröffentlichung vorhanden.';
|
||||
} else if (info.available) {
|
||||
st.textContent = `Version ${formatVersionLabel(info.latestVersion)} verfügbar.`;
|
||||
} else {
|
||||
st.classList.add('status-ok');
|
||||
st.textContent = `✓ Du hast bereits die neueste Version (${formatVersionLabel(info.currentVersion)}).`;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Zustand der Anmelde-Dienste in der Statusleiste ----------
|
||||
function showAuthHealth(h) {
|
||||
const box = el('status-auth');
|
||||
@@ -1135,11 +1296,15 @@ function showAuthHealth(h) {
|
||||
const AUTH_SERVICE_COUNT = 3;
|
||||
|
||||
// ---------- Skin-Hintergrund (wie MultiMCs Katze, nur mit eigenem Skin) ----------
|
||||
async function applySkinBackground() {
|
||||
// uuidOverride: wird vom Konten-Wechsler (siehe unten) genutzt, um gezielt
|
||||
// einen bestimmten Skin zu zeigen, ohne die eigentliche Einstellung anzufassen
|
||||
async function applySkinBackground(uuidOverride) {
|
||||
const s = await window.api.getSettings();
|
||||
const root = document.documentElement;
|
||||
let uuid = '';
|
||||
if (s.bgSkin === 'active') {
|
||||
if (uuidOverride) {
|
||||
uuid = uuidOverride;
|
||||
} else if (s.bgSkin === 'active') {
|
||||
const acc = await window.api.authStatus();
|
||||
uuid = acc && acc.profile ? acc.profile.id : '';
|
||||
} else if (s.bgSkin) {
|
||||
@@ -1172,6 +1337,44 @@ async function applySkinBackground() {
|
||||
root.style.setProperty('--bg-skin', 'none');
|
||||
}
|
||||
|
||||
// ---------- Skin-Hintergrund: automatischer Kontenwechsel ----------
|
||||
let skinCycleTimer = null;
|
||||
let skinCycleIndex = 0;
|
||||
|
||||
function stopSkinCycle() {
|
||||
if (skinCycleTimer) { clearInterval(skinCycleTimer); skinCycleTimer = null; }
|
||||
}
|
||||
|
||||
async function skinCycleTick() {
|
||||
const accs = await window.api.authAccounts();
|
||||
if (accs.length < 2) { stopSkinCycle(); await applySkinBackground(); return; }
|
||||
skinCycleIndex = (skinCycleIndex + 1) % accs.length; // am Ende wieder von vorn
|
||||
await applySkinBackground(accs[skinCycleIndex].id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Einstiegspunkt für alles, was den Skin-Hintergrund neu anzeigen soll.
|
||||
* Ersetzt die frühere direkte applySkinBackground()-Aufrufe: kümmert sich
|
||||
* zusätzlich darum, den Kontenwechsel-Timer je nach Einstellung zu
|
||||
* starten/stoppen, statt dass jede Aufrufstelle das selbst tun müsste.
|
||||
*/
|
||||
async function refreshSkinBackground() {
|
||||
stopSkinCycle();
|
||||
const s = await window.api.getSettings();
|
||||
if (s.bgSkinCycle) {
|
||||
const accs = await window.api.authAccounts();
|
||||
if (accs.length >= 2) {
|
||||
skinCycleIndex = 0;
|
||||
await applySkinBackground(accs[0].id);
|
||||
const seconds = Math.max(5, Number(s.bgSkinCycleSeconds) || 30);
|
||||
skinCycleTimer = setInterval(skinCycleTick, seconds * 1000);
|
||||
return;
|
||||
}
|
||||
// weniger als 2 Konten angemeldet -> normal weiter wie ohne Wechsel
|
||||
}
|
||||
await applySkinBackground();
|
||||
}
|
||||
|
||||
// Pose-Vorlagen für den lokalen 3D-Renderer (eigene Nachbildungen bekannter Motive)
|
||||
const SKIN_POSES = {
|
||||
walking: { label: 'Laufend', fov: 36, zoom: 0.62, apply: (p) => {
|
||||
@@ -1936,9 +2139,11 @@ async function renderServers() {
|
||||
<div class="mod-info">
|
||||
<div class="mod-name">${escapeHtml(s.name || s.ip)}</div>
|
||||
<div class="mod-meta">${escapeHtml(s.ip)}</div>
|
||||
<div class="mod-meta srv-status hint">Wird geprüft …</div>
|
||||
</div>
|
||||
<div class="mod-action"></div>`;
|
||||
const aktionen = item.querySelector('.mod-action');
|
||||
pingServerRow(s.ip, item.querySelector('.srv-status'));
|
||||
|
||||
const starten = document.createElement('button');
|
||||
starten.className = 'btn btn-primary btn-mini';
|
||||
@@ -1992,6 +2197,24 @@ async function renderServers() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fragt Live-Status (MOTD/Spieler/Version) ab und trägt ihn in die Zeile ein.
|
||||
// Läuft unabhängig für jede Zeile, ohne die Liste selbst zu blockieren.
|
||||
async function pingServerRow(ip, statusEl) {
|
||||
if (!statusEl) return;
|
||||
const res = await window.api.serversPing(ip);
|
||||
// Zeile kann inzwischen aus dem DOM entfernt worden sein (Liste neu aufgebaut)
|
||||
if (!statusEl.isConnected) return;
|
||||
if (!res.ok) {
|
||||
statusEl.textContent = '⚠ ' + (res.message || 'nicht erreichbar');
|
||||
statusEl.classList.add('status-bad');
|
||||
return;
|
||||
}
|
||||
const spieler = res.max != null ? `${res.online ?? '?'}/${res.max} Spieler` : '';
|
||||
statusEl.textContent = [res.motd, spieler, res.version, res.latencyMs + ' ms']
|
||||
.filter(Boolean).join(' · ');
|
||||
statusEl.classList.add('status-ok');
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -2130,7 +2353,7 @@ async function setzeSkin() {
|
||||
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
|
||||
await refreshSkinBackground(); // auch der Hintergrund zeigt jetzt den neuen Skin
|
||||
} else {
|
||||
el('skin-status').textContent = '✖ ' + (res.message || 'Fehlgeschlagen.');
|
||||
btn.disabled = false;
|
||||
@@ -2145,7 +2368,7 @@ async function setzeSkinZurueck() {
|
||||
if (res.ok) {
|
||||
toast('Skin auf Standard zurückgesetzt.');
|
||||
await openSkin();
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
} else {
|
||||
el('skin-status').textContent = '✖ ' + (res.message || 'Fehlgeschlagen.');
|
||||
}
|
||||
@@ -2431,6 +2654,24 @@ function renderContentList(boxId, items, sub, emoji) {
|
||||
`<div class="mod-icon">${emoji}</div>
|
||||
<div class="mod-info"><div class="mod-name">${escapeHtml(it.name)}</div></div>
|
||||
<div class="mod-action"></div>`;
|
||||
const aktionen = item.querySelector('.mod-action');
|
||||
if (sub === 'saves') {
|
||||
const sichern = document.createElement('button');
|
||||
sichern.className = 'btn btn-mini';
|
||||
sichern.textContent = 'Sichern';
|
||||
sichern.title = 'Nur diese Welt jetzt als ZIP sichern (Dokumente\\AeroMC Launcher\\Welten-Sicherungen)';
|
||||
sichern.addEventListener('click', async () => {
|
||||
sichern.disabled = true;
|
||||
const orig = sichern.textContent;
|
||||
sichern.textContent = 'Sichere …';
|
||||
const res = await window.api.backupWorld(currentDetailId, it.name);
|
||||
sichern.disabled = false;
|
||||
sichern.textContent = orig;
|
||||
if (res.ok) toast(`„${it.name}" gesichert.`);
|
||||
else toast(res.message || 'Sicherung fehlgeschlagen.', true);
|
||||
});
|
||||
aktionen.appendChild(sichern);
|
||||
}
|
||||
const rm = document.createElement('button');
|
||||
rm.className = 'btn btn-danger btn-mini';
|
||||
rm.textContent = 'Löschen';
|
||||
@@ -2438,7 +2679,7 @@ function renderContentList(boxId, items, sub, emoji) {
|
||||
'Löschen', `„${it.name}" wird unwiderruflich gelöscht. Fortfahren?`, '🗑 Löschen',
|
||||
async () => { await window.api.deleteContent(currentDetailId, sub, it.name); await renderContentLists(); toast('Gelöscht: ' + it.name); },
|
||||
));
|
||||
item.querySelector('.mod-action').appendChild(rm);
|
||||
aktionen.appendChild(rm);
|
||||
box.appendChild(item);
|
||||
}
|
||||
}
|
||||
@@ -2586,9 +2827,24 @@ async function refreshInstalled() {
|
||||
btn.className = 'btn btn-danger btn-mini';
|
||||
btn.textContent = 'Entfernen';
|
||||
btn.addEventListener('click', async () => {
|
||||
await window.api.modRemove(currentDetailId, m.filename, modArt);
|
||||
toast('Entfernt: ' + m.title);
|
||||
await refreshInstalled();
|
||||
const tatsaechlichEntfernen = async () => {
|
||||
await window.api.modRemove(currentDetailId, m.filename, modArt);
|
||||
toast('Entfernt: ' + m.title);
|
||||
await refreshInstalled();
|
||||
};
|
||||
const abhaenger = await window.api.modDependents(currentDetailId, m.filename, modArt);
|
||||
if (abhaenger.length) {
|
||||
const namen = abhaenger.map((n) => `„${n}"`).join(', ');
|
||||
askConfirm(
|
||||
'Wird noch gebraucht',
|
||||
`${namen} ${abhaenger.length === 1 ? 'benötigt' : 'benötigen'} „${m.title}" als Pflicht-Abhängigkeit. `
|
||||
+ 'Wird sie trotzdem entfernt, funktionieren diese Mods vermutlich nicht mehr richtig. Trotzdem entfernen?',
|
||||
'Trotzdem entfernen',
|
||||
tatsaechlichEntfernen,
|
||||
);
|
||||
} else {
|
||||
await tatsaechlichEntfernen();
|
||||
}
|
||||
});
|
||||
aktionen.appendChild(btn);
|
||||
box.appendChild(item);
|
||||
@@ -2791,9 +3047,13 @@ async function openSettings(pane) {
|
||||
}
|
||||
sel.value = s.bgSkin || '';
|
||||
el('s-bgskin-pose').value = s.bgSkinPose || 'walking';
|
||||
el('s-bgskin-cycle').checked = !!s.bgSkinCycle;
|
||||
el('s-bgskin-cycle-seconds').value = s.bgSkinCycleSeconds || 30;
|
||||
sel.disabled = !!s.bgSkinCycle;
|
||||
el('s-sortby').value = s.sortBy || 'lastPlayed';
|
||||
el('s-iso3d').checked = s.iso3dIcons !== false;
|
||||
el('s-bedrock').checked = s.bedrockEnabled !== false;
|
||||
el('s-notify').checked = s.desktopNotifications !== false;
|
||||
el('s-auto-java').checked = s.autoJava !== false;
|
||||
el('s-autobackup').checked = !!s.autoBackupEnabled;
|
||||
el('s-autobackup-every').value = s.autoBackupEvery || 'daily';
|
||||
@@ -2908,9 +3168,12 @@ async function saveSettings() {
|
||||
globalPostExit: el('s-global-post').value.trim(),
|
||||
bgSkin: el('s-bgskin').value,
|
||||
bgSkinPose: el('s-bgskin-pose').value,
|
||||
bgSkinCycle: el('s-bgskin-cycle').checked,
|
||||
bgSkinCycleSeconds: Math.max(5, numOrNull(el('s-bgskin-cycle-seconds').value) || 30),
|
||||
sortBy: el('s-sortby').value,
|
||||
iso3dIcons: el('s-iso3d').checked,
|
||||
bedrockEnabled: el('s-bedrock').checked,
|
||||
desktopNotifications: el('s-notify').checked,
|
||||
autoJava: el('s-auto-java').checked,
|
||||
autoBackupEnabled: el('s-autobackup').checked,
|
||||
autoBackupEvery: el('s-autobackup-every').value,
|
||||
@@ -2942,7 +3205,7 @@ async function saveSettings() {
|
||||
applyTheme(patch.theme);
|
||||
iso3dEnabled = patch.iso3dIcons;
|
||||
await applyBedrockVisibility();
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
hide('modal-settings');
|
||||
toast('Einstellungen gespeichert.');
|
||||
await reload();
|
||||
@@ -3180,7 +3443,7 @@ async function runGlobalRestore() {
|
||||
applyTheme(s.theme || 'dark');
|
||||
iso3dEnabled = s.iso3dIcons !== false;
|
||||
await applyBedrockVisibility();
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
hide('modal-settings');
|
||||
await reload();
|
||||
toast('Backup wiederhergestellt: ' + res.path);
|
||||
@@ -3267,7 +3530,7 @@ async function renderAccounts(boxId = 'login-accounts') {
|
||||
await window.api.authSetActive(a.id);
|
||||
await refreshAllAccountLists();
|
||||
await refreshAccountButton();
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
toast('Aktiv: ' + a.name);
|
||||
});
|
||||
actions.appendChild(use);
|
||||
@@ -3392,8 +3655,9 @@ function wire() {
|
||||
// Skin-Hintergrund an/aus (wie MultiMCs Katzen-Knopf)
|
||||
el('btn-skin').addEventListener('click', async () => {
|
||||
const s = await window.api.getSettings();
|
||||
if (s.bgSkin) {
|
||||
await window.api.saveSettings({ bgSkin: '' });
|
||||
if (s.bgSkin || s.bgSkinCycle) {
|
||||
// auch den Kontenwechsel mit ausschalten, sonst würde er sofort wieder anspringen
|
||||
await window.api.saveSettings({ bgSkin: '', bgSkinCycle: false });
|
||||
toast('Skin-Hintergrund aus.');
|
||||
} else {
|
||||
const acc = await window.api.authStatus();
|
||||
@@ -3401,7 +3665,7 @@ function wire() {
|
||||
await window.api.saveSettings({ bgSkin: 'active' });
|
||||
toast('Skin-Hintergrund an: ' + acc.profile.name);
|
||||
}
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
});
|
||||
|
||||
// Kontextmenü: Einträge lösen die Aktionen der rechten Leiste aus
|
||||
@@ -3442,6 +3706,13 @@ function wire() {
|
||||
searchModpacks();
|
||||
}
|
||||
}
|
||||
if (tab.dataset.kind === 'technic') {
|
||||
const box = el('tp-results');
|
||||
if (box && !box.dataset.loaded) {
|
||||
box.dataset.loaded = '1';
|
||||
searchTechnicPlatform();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
el('import-zip-do').addEventListener('click', runZipImport);
|
||||
@@ -3485,6 +3756,23 @@ function wire() {
|
||||
});
|
||||
el('mmc-list').addEventListener('change', updateMmcButton);
|
||||
el('mmc-do').addEventListener('click', runMmcImport);
|
||||
el('tp-search-btn').addEventListener('click', searchTechnicPlatform);
|
||||
el('tp-query').addEventListener('keydown', (e) => { if (e.key === 'Enter') searchTechnicPlatform(); });
|
||||
window.api.onTechnicPlatformProgress((p) => {
|
||||
el('tp-status').textContent = `Lade Mods … ${p.current}/${p.total}${p.name ? ' (' + p.name + ')' : ''}`;
|
||||
});
|
||||
el('technic-pick').addEventListener('click', async () => {
|
||||
const p = await window.api.technicPickFolder();
|
||||
if (p) { el('technic-path').value = p; await scanTechnic(); }
|
||||
});
|
||||
el('technic-scan').addEventListener('click', scanTechnic);
|
||||
el('technic-path').addEventListener('keydown', (e) => { if (e.key === 'Enter') scanTechnic(); });
|
||||
el('technic-all').addEventListener('change', (e) => {
|
||||
document.querySelectorAll('#technic-list input[type=checkbox]').forEach((c) => { c.checked = e.target.checked; });
|
||||
updateTechnicButton();
|
||||
});
|
||||
el('technic-list').addEventListener('change', updateTechnicButton);
|
||||
el('technic-do').addEventListener('click', runTechnicImport);
|
||||
window.api.onMcProgress((p) => {
|
||||
el('mc-progress').textContent = `Übernehme … ${p.current}/${p.total}`;
|
||||
el('mc-progress').classList.remove('hidden');
|
||||
@@ -3497,13 +3785,33 @@ function wire() {
|
||||
el('mmc-progress').textContent = `Übernehme … ${p.current}/${p.total}`;
|
||||
el('mmc-progress').classList.remove('hidden');
|
||||
});
|
||||
window.api.onTechnicProgress((p) => {
|
||||
el('technic-progress').textContent = `Übernehme … ${p.current}/${p.total}`;
|
||||
el('technic-progress').classList.remove('hidden');
|
||||
});
|
||||
|
||||
// Bedrock
|
||||
el('btn-bedrock').addEventListener('click', openBedrock);
|
||||
el('bd-play').addEventListener('click', async () => {
|
||||
const res = await window.api.bedrockLaunch();
|
||||
if (res && res.ok) toast('Bedrock wird gestartet …');
|
||||
else toast('Start fehlgeschlagen: ' + ((res && res.message) || 'unbekannter Fehler'), true);
|
||||
const btn = el('bd-play');
|
||||
// Ein Klick löst eine nicht abbrechbare App-Aktivierung aus, die etwas
|
||||
// dauern kann – Button sperren, damit ein ungeduldiger zweiter Klick
|
||||
// nicht zu einem doppelt geöffneten Spiel führt.
|
||||
if (btn.disabled) return;
|
||||
btn.disabled = true;
|
||||
toast('Bedrock wird gestartet …');
|
||||
try {
|
||||
const res = await window.api.bedrockLaunch();
|
||||
if (res && res.ok) {
|
||||
if (!res.confirmed) {
|
||||
toast('Start ausgelöst, konnte aber nicht bestätigt werden. Falls sich nichts öffnet, bitte erneut versuchen.');
|
||||
}
|
||||
} else {
|
||||
toast('Start fehlgeschlagen: ' + ((res && res.message) || 'unbekannter Fehler'), true);
|
||||
}
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
el('bd-import').addEventListener('click', async () => {
|
||||
const res = await window.api.bedrockInstallAddon();
|
||||
@@ -3673,6 +3981,16 @@ function wire() {
|
||||
});
|
||||
el('crash-console').addEventListener('click', () => { hide('modal-crash'); openConsole(); });
|
||||
|
||||
el('m-add-file').addEventListener('click', async () => {
|
||||
const res = await window.api.modAddFile(currentDetailId, modArt);
|
||||
if (res.canceled) return;
|
||||
if (!res.ok) { toast(res.message || 'Datei konnte nicht hinzugefügt werden.', true); return; }
|
||||
const n = res.added.length;
|
||||
const f = res.failed.length;
|
||||
if (n) toast(`${n} Datei${n === 1 ? '' : 'en'} hinzugefügt${f ? ` · ${f} fehlgeschlagen` : ''}.`, f > 0 && !n);
|
||||
else if (f) toast(res.failed[0].message || 'Hinzufügen fehlgeschlagen.', true);
|
||||
await refreshInstalled();
|
||||
});
|
||||
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})`));
|
||||
@@ -3697,10 +4015,20 @@ function wire() {
|
||||
bgSkin: el('s-bgskin').value,
|
||||
bgSkinPose: el('s-bgskin-pose').value,
|
||||
});
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
};
|
||||
el('s-bgskin').addEventListener('change', previewSkin);
|
||||
el('s-bgskin-pose').addEventListener('change', previewSkin);
|
||||
const previewSkinCycle = async () => {
|
||||
el('s-bgskin').disabled = el('s-bgskin-cycle').checked;
|
||||
await window.api.saveSettings({
|
||||
bgSkinCycle: el('s-bgskin-cycle').checked,
|
||||
bgSkinCycleSeconds: Math.max(5, numOrNull(el('s-bgskin-cycle-seconds').value) || 30),
|
||||
});
|
||||
await refreshSkinBackground();
|
||||
};
|
||||
el('s-bgskin-cycle').addEventListener('change', previewSkinCycle);
|
||||
el('s-bgskin-cycle-seconds').addEventListener('change', previewSkinCycle);
|
||||
el('s-scan-java').addEventListener('click', scanJava);
|
||||
el('f-java-detected').addEventListener('change', (e) => {
|
||||
if (e.target.value) el('f-java').value = e.target.value;
|
||||
@@ -3709,6 +4037,13 @@ function wire() {
|
||||
const p = await window.api.pickJava();
|
||||
if (p) { el('f-java').value = p; el('f-java-detected').value = ''; }
|
||||
});
|
||||
el('f-extra-args-aikar').addEventListener('click', () => {
|
||||
const feld = el('f-extra-args');
|
||||
const vorhanden = feld.value.trim();
|
||||
if (vorhanden.includes('G1NewSizePercent')) { toast('Die GC-Flags stehen schon drin.'); return; }
|
||||
feld.value = vorhanden ? vorhanden + ' ' + AIKAR_FLAGS : AIKAR_FLAGS;
|
||||
toast('GC-Flags eingefügt.');
|
||||
});
|
||||
el('s-java-detected').addEventListener('change', async (e) => {
|
||||
if (e.target.value) { el('s-java').value = e.target.value; await refreshJavaStatus(e.target.value); }
|
||||
});
|
||||
@@ -3744,18 +4079,10 @@ function wire() {
|
||||
el('upd-open').addEventListener('click', openReleasePage);
|
||||
if (el('s-check-update')) {
|
||||
el('s-check-update').addEventListener('click', async () => {
|
||||
const st = el('s-update-status');
|
||||
st.textContent = 'Suche nach Updates …';
|
||||
renderUpdateStatus({ checking: true });
|
||||
const info = await window.api.updateCheck();
|
||||
if (info.error) { st.textContent = '✖ ' + info.error; return; }
|
||||
if (info.noReleases) { st.textContent = 'Noch keine Veröffentlichung vorhanden.'; return; }
|
||||
if (info.available) {
|
||||
st.textContent = `Version ${formatVersionLabel(info.latestVersion)} verfügbar.`;
|
||||
hide('modal-settings');
|
||||
showUpdateDialog(info);
|
||||
} else {
|
||||
st.textContent = `Du hast bereits die neueste Version (${formatVersionLabel(info.currentVersion)}).`;
|
||||
}
|
||||
renderUpdateStatus(info);
|
||||
if (info.available) { hide('modal-settings'); showUpdateDialog(info); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3818,8 +4145,93 @@ function wire() {
|
||||
if (!el('modal-prompt').classList.contains('hidden')) { closePrompt(null); return; }
|
||||
const open = [...document.querySelectorAll('.modal:not(.hidden)')].pop();
|
||||
if (open) open.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const strg = e.ctrlKey || e.metaKey;
|
||||
const modalOffen = document.querySelector('.modal:not(.hidden)');
|
||||
const inEingabe = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)
|
||||
|| document.activeElement.isContentEditable;
|
||||
|
||||
// Strg+F: Suche fokussieren – nicht wenn gerade ein Dialog offen ist,
|
||||
// der eine eigene Suche hat (Mods, Modpack-Katalog, Import …)
|
||||
if (strg && e.key.toLowerCase() === 'f' && !modalOffen) {
|
||||
e.preventDefault();
|
||||
el('search').focus();
|
||||
el('search').select();
|
||||
return;
|
||||
}
|
||||
// Strg+N: neue Instanz anlegen
|
||||
if (strg && e.key.toLowerCase() === 'n' && !modalOffen) {
|
||||
e.preventDefault();
|
||||
el('btn-new').click();
|
||||
return;
|
||||
}
|
||||
// Entf: markierte Instanz(en) löschen – nicht während man irgendwo tippt
|
||||
if (e.key === 'Delete' && !modalOffen && !inEingabe) {
|
||||
e.preventDefault();
|
||||
el('p-delete').click();
|
||||
}
|
||||
});
|
||||
wireFileDrop();
|
||||
}
|
||||
|
||||
// ---------- Datei-Drag&Drop aufs Fenster (.jar/.mrpack/.zip) ----------
|
||||
function wireFileDrop() {
|
||||
let dragDepth = 0; // dragenter/-leave feuern auch für Kindelemente
|
||||
const istDatei = (e) => e.dataTransfer && [...e.dataTransfer.types].includes('Files');
|
||||
|
||||
window.addEventListener('dragenter', (e) => {
|
||||
if (!istDatei(e)) return;
|
||||
e.preventDefault();
|
||||
dragDepth++;
|
||||
document.body.classList.add('file-drop-active');
|
||||
});
|
||||
window.addEventListener('dragover', (e) => {
|
||||
if (!istDatei(e)) return;
|
||||
e.preventDefault();
|
||||
});
|
||||
window.addEventListener('dragleave', (e) => {
|
||||
if (!istDatei(e)) return;
|
||||
dragDepth = Math.max(0, dragDepth - 1);
|
||||
if (dragDepth === 0) document.body.classList.remove('file-drop-active');
|
||||
});
|
||||
window.addEventListener('drop', async (e) => {
|
||||
if (!istDatei(e)) return;
|
||||
e.preventDefault();
|
||||
dragDepth = 0;
|
||||
document.body.classList.remove('file-drop-active');
|
||||
const files = [...(e.dataTransfer.files || [])].filter((f) => f.path);
|
||||
for (const f of files) await handleDroppedFile(f.path);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDroppedFile(filePath) {
|
||||
const ext = ('.' + filePath.split('.').pop()).toLowerCase();
|
||||
if (!['.jar', '.mrpack', '.zip'].includes(ext)) {
|
||||
toast('Nicht unterstützter Dateityp: ' + filePath.split(/[\\/]/).pop(), true);
|
||||
return;
|
||||
}
|
||||
// Für .jar: läuft der Mods-Dialog gerade, zählt die dort gewählte
|
||||
// Inhaltsart (Mod/Ressourcenpaket/Shader) – sonst landet es als Mod
|
||||
// direkt in der aktuell ausgewählten Instanz.
|
||||
const modsOpen = !el('modal-mods').classList.contains('hidden');
|
||||
const targetArt = modsOpen ? modArt : 'mod';
|
||||
const res = await window.api.filesDrop(filePath, currentDetailId, targetArt);
|
||||
if (!res.ok) { toast(res.message || 'Import fehlgeschlagen.', true); return; }
|
||||
|
||||
if (res.kind === 'mod') {
|
||||
toast(`Datei hinzugefügt: ${res.added[0]}`);
|
||||
if (modsOpen) await refreshInstalled();
|
||||
return;
|
||||
}
|
||||
await reload();
|
||||
if (res.instance) {
|
||||
toast(`Instanz übernommen: ${res.instance.name}`);
|
||||
await selectInstance(res.instance.id);
|
||||
} else {
|
||||
toast('Import abgeschlossen.');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
@@ -3831,11 +4243,11 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
iso3dEnabled = s.iso3dIcons !== false;
|
||||
consoleMaxChars = Math.max(50000, (s.consoleMaxLines || 100000) * 3);
|
||||
} catch { applyTheme('dark'); }
|
||||
await applySkinBackground();
|
||||
await refreshSkinBackground();
|
||||
window.api.onLaunchProgress(onLaunchEvt);
|
||||
window.api.onBackupProgress((p) => applyBackupProgressEvent(p.mode, p.percent || 0, p.detail || ''));
|
||||
window.api.onAuthCode(onAuthCode);
|
||||
window.api.onAuthRefreshed(async () => { await refreshAccountButton(); await applySkinBackground(); });
|
||||
window.api.onAuthRefreshed(async () => { await refreshAccountButton(); await refreshSkinBackground(); });
|
||||
window.api.onAuthHealth(showAuthHealth);
|
||||
// Spiel gestartet oder beendet -> "spielt gerade" in den Kontenlisten nachziehen
|
||||
window.api.onAccountsChanged(() => { refreshAllAccountLists(); });
|
||||
@@ -3846,6 +4258,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
});
|
||||
window.api.onUpdateAvailable(showUpdateDialog);
|
||||
window.api.onUpdateProgress(onUpdateProgress);
|
||||
window.api.onUpdateChecked(renderUpdateStatus);
|
||||
try { showAuthHealth(await window.api.authHealth()); } catch { /* noch keine Prüfung */ }
|
||||
await loadMcIcons(); // echte Minecraft-Icons bereitstellen
|
||||
await applyBedrockVisibility();
|
||||
|
||||
Reference in New Issue
Block a user