Upload via GUI (45 Dateien)
This commit is contained in:
+132
-15
@@ -467,7 +467,7 @@ function iconOf(key, size = 44) {
|
||||
|
||||
const LOADER_LABEL = {
|
||||
vanilla: 'Vanilla', fabric: 'Fabric', forge: 'Forge',
|
||||
neoforge: 'NeoForge', quilt: 'Quilt',
|
||||
neoforge: 'NeoForge', quilt: 'Quilt', optifine: 'OptiFine',
|
||||
};
|
||||
|
||||
// Bewährte Garbage-Collector-Flags (bekannt als "Aikar's Flags"), reduzieren
|
||||
@@ -645,17 +645,28 @@ async function searchModpacks() {
|
||||
const query = el('mp-query').value.trim();
|
||||
const gameVersion = el('mp-mc').value.trim();
|
||||
const sort = el('mp-sort').value || 'relevance';
|
||||
const source = (el('mp-source') && el('mp-source').value) || 'modrinth';
|
||||
const box = el('mp-results');
|
||||
const st = el('mp-status');
|
||||
st.textContent = 'Suche …';
|
||||
box.innerHTML = '<div class="mods-empty">Suche läuft …</div>';
|
||||
try {
|
||||
const res = await window.api.modpackSearch({
|
||||
query, gameVersion, sort, limit: 30, offset: 0, art: 'modpack',
|
||||
query, gameVersion, sort, limit: 30, offset: 0, art: 'modpack', source,
|
||||
});
|
||||
if (res.error) {
|
||||
st.textContent = 'Fehler: ' + res.error;
|
||||
box.innerHTML = '<div class="mods-empty">Suche fehlgeschlagen.</div>';
|
||||
if (res.needKey) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'mods-empty';
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-mini';
|
||||
btn.textContent = 'Zu den Netzwerk-Einstellungen';
|
||||
btn.addEventListener('click', () => { hide('modal-import'); openSettings('pane-network'); });
|
||||
wrap.appendChild(btn);
|
||||
box.appendChild(wrap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const hits = res.hits || [];
|
||||
@@ -703,11 +714,15 @@ async function installModpackFromSearch(hit, btn) {
|
||||
if (btn) { btn.disabled = true; btn.textContent = '…'; }
|
||||
status('Installiere Modpack: ' + (hit.title || hit.slug || '') + ' …');
|
||||
try {
|
||||
const res = await window.api.modpackInstallFromModrinth({
|
||||
projectId: hit.projectId,
|
||||
title: hit.title || hit.slug,
|
||||
gameVersion,
|
||||
});
|
||||
const res = hit.source === 'curseforge'
|
||||
? await window.api.modpackInstallFromCurseForge({
|
||||
cfId: hit.cfId, projectId: hit.projectId, title: hit.title || hit.slug, gameVersion,
|
||||
})
|
||||
: await window.api.modpackInstallFromModrinth({
|
||||
projectId: hit.projectId,
|
||||
title: hit.title || hit.slug,
|
||||
gameVersion,
|
||||
});
|
||||
status('Bereit'); el('status-right').textContent = '';
|
||||
if (res.ok) {
|
||||
await reload();
|
||||
@@ -1786,7 +1801,44 @@ async function updateLoaderVersions(preselect) {
|
||||
const hint = el('f-loader-hint');
|
||||
sel.innerHTML = '<option value="">neueste (empfohlen)</option>';
|
||||
|
||||
const ofTools = el('f-optifine-tools');
|
||||
if (ofTools) ofTools.classList.toggle('hidden', loader !== 'optifine');
|
||||
|
||||
if (loader === 'vanilla') { sel.disabled = true; hint.textContent = 'Vanilla hat keinen Loader.'; return; }
|
||||
|
||||
if (loader === 'optifine') {
|
||||
// Kein Loader-API bei OptiFine – die Liste zeigt bereits eingerichtete
|
||||
// Fassungen (siehe f-optifine-tools weiter unten), keine automatisch
|
||||
// ladbaren Versionen.
|
||||
sel.innerHTML = '<option value="">– keine gewählt –</option>';
|
||||
sel.disabled = false;
|
||||
if (el('f-optifine-status')) el('f-optifine-status').textContent = '';
|
||||
if (el('f-optifine-confirm')) el('f-optifine-confirm').classList.add('hidden');
|
||||
if (!editingId) {
|
||||
hint.textContent = 'Erst speichern, dann lässt sich OptiFine im Bearbeiten-Dialog einrichten.';
|
||||
if (el('f-optifine-install')) el('f-optifine-install').disabled = true;
|
||||
return;
|
||||
}
|
||||
if (el('f-optifine-install')) el('f-optifine-install').disabled = false;
|
||||
if (!mc) { hint.textContent = 'Erst Minecraft-Version wählen.'; return; }
|
||||
hint.textContent = 'Suche bereits eingerichtete OptiFine-Fassungen …';
|
||||
try {
|
||||
const found = await window.api.optifineFind(mc);
|
||||
for (const v of found) {
|
||||
const o = document.createElement('option');
|
||||
o.value = v; o.textContent = v;
|
||||
if (v === preselect) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
hint.textContent = found.length
|
||||
? `${found.length} eingerichtete OptiFine-Fassung(en) für ${mc}.`
|
||||
: 'Noch keine OptiFine-Installation für diese Version – über den Knopf unten einrichten.';
|
||||
} catch {
|
||||
hint.textContent = 'Suche fehlgeschlagen.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sel.disabled = false;
|
||||
if (!mc) { hint.textContent = 'Erst Minecraft-Version wählen.'; return; }
|
||||
|
||||
@@ -2709,14 +2761,23 @@ function renderContentList(boxId, items, sub, emoji) {
|
||||
|
||||
// Welche Inhaltsart wird gerade gezeigt: mod | resourcepack | shader
|
||||
let modArt = 'mod';
|
||||
const ART_LABEL = { mod: 'Mods', resourcepack: 'Ressourcenpakete', shader: 'Shader' };
|
||||
const ART_LABEL = {
|
||||
mod: 'Mods', resourcepack: 'Ressourcenpakete', shader: 'Shader', datapack: 'Datenpakete',
|
||||
};
|
||||
const ART_SUCHE = {
|
||||
mod: 'Mods durchsuchen … (z. B. Sodium, JEI, Create)',
|
||||
resourcepack: 'Ressourcenpakete durchsuchen … (z. B. Faithful, Bare Bones)',
|
||||
shader: 'Shader durchsuchen … (z. B. Complementary, BSL)',
|
||||
datapack: 'Datenpakete durchsuchen … (z. B. Terralith, Vanilla Tweaks)',
|
||||
};
|
||||
// Nur Mods brauchen einen Loader – Ressourcenpakete und Shader laufen auch in Vanilla
|
||||
const ART_BRAUCHT_LOADER = { mod: true, resourcepack: false, shader: false };
|
||||
// Nur Mods brauchen einen Loader – Ressourcenpakete, Shader und Datenpakete laufen auch in Vanilla
|
||||
const ART_BRAUCHT_LOADER = {
|
||||
mod: true, resourcepack: false, shader: false, datapack: false,
|
||||
};
|
||||
// CurseForge hat für Datenpakete keinen verlässlich zugeordneten API-Klassencode in
|
||||
// diesem Launcher – dort deshalb nur Modrinth als Quelle anbieten, statt falsche
|
||||
// Treffer (z. B. Mods) unter "Datenpakete" anzuzeigen.
|
||||
const ART_NUR_MODRINTH = { datapack: true };
|
||||
|
||||
async function openMods() {
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
@@ -2754,6 +2815,10 @@ async function zeigeModAnsicht(inst) {
|
||||
['m-query', 'm-search-btn', 'm-sort', 'm-category'].forEach((id) => { el(id).disabled = blocked; });
|
||||
// Kategorien gelten nur für Mods
|
||||
el('m-category').disabled = blocked || modArt !== 'mod';
|
||||
// Manche Quellen decken nicht jede Inhaltsart ab (z. B. CurseForge <-> Datenpakete)
|
||||
const cfOption = el('m-source').querySelector('option[value="curseforge"]');
|
||||
if (cfOption) cfOption.disabled = !!ART_NUR_MODRINTH[modArt];
|
||||
if (ART_NUR_MODRINTH[modArt] && el('m-source').value === 'curseforge') el('m-source').value = 'modrinth';
|
||||
el('m-query').value = '';
|
||||
el('m-more').classList.add('hidden');
|
||||
// Ergebnisse der vorigen Ansicht nicht mitschleppen
|
||||
@@ -2813,9 +2878,13 @@ async function refreshInstalled() {
|
||||
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>');
|
||||
// OptiFine lässt sich nicht über Modrinth/CurseForge installieren (kein API-Zugriff auf
|
||||
// deren Downloads) – von Hand hinzugefügt taucht es sonst wie ein beliebiger Mod auf.
|
||||
const istOptiFine = modArt === 'mod' && /^optifine[_-]/i.test(m.filename || '');
|
||||
if (istOptiFine) kennzeichen.push('<span class="mod-off" style="opacity:.8">OptiFine</span>');
|
||||
|
||||
item.innerHTML =
|
||||
`<div class="mod-icon">${aus ? '🚫' : '🧩'}</div>
|
||||
`<div class="mod-icon">${aus ? '🚫' : istOptiFine ? '⚡' : '🧩'}</div>
|
||||
<div class="mod-info">
|
||||
<div class="mod-name">${escapeHtml(m.title)}${kennzeichen.length ? ' ' + kennzeichen.join(' ') : ''}</div>
|
||||
<div class="mod-meta">${up
|
||||
@@ -3123,17 +3192,24 @@ async function openSettings(pane) {
|
||||
['Autor', info.author || 'Nicht hinterlegt'],
|
||||
['Webseite', info.homepage || 'Nicht hinterlegt', info.homepage || ''],
|
||||
['Launcher-Webseite', info.launcherWebsite || 'Nicht hinterlegt', info.launcherWebsite || ''],
|
||||
].map(([label, value, url]) => {
|
||||
['Lizenz', 'Nur privater, nicht-kommerzieller Gebrauch – Details anzeigen', null, 'license'],
|
||||
].map(([label, value, url, action]) => {
|
||||
const renderedValue = url
|
||||
? `<a href="#" class="info-meta-link" data-url="${escapeHtml(url)}">${escapeHtml(value)}</a>`
|
||||
: escapeHtml(value || '–');
|
||||
: action
|
||||
? `<a href="#" class="info-meta-link" data-action="${escapeHtml(action)}">${escapeHtml(value)}</a>`
|
||||
: escapeHtml(value || '–');
|
||||
return `<div class="settings-kv-row"><span class="settings-kv-label">${escapeHtml(label)}</span><span class="settings-kv-value">${renderedValue}</span></div>`;
|
||||
}).join('');
|
||||
for (const link of el('s-appinfo').querySelectorAll('.info-meta-link')) {
|
||||
link.addEventListener('click', (evt) => {
|
||||
link.addEventListener('click', async (evt) => {
|
||||
evt.preventDefault();
|
||||
const url = link.dataset.url;
|
||||
if (url) window.api.openExternal(url);
|
||||
if (url) { window.api.openExternal(url); return; }
|
||||
if (link.dataset.action === 'license') {
|
||||
show('modal-license');
|
||||
el('license-text').textContent = await window.api.appLicense();
|
||||
}
|
||||
});
|
||||
}
|
||||
show('modal-settings');
|
||||
@@ -3859,6 +3935,47 @@ function wire() {
|
||||
el('f-version-type').addEventListener('change', () => fillVersionSelect(el('f-version').value));
|
||||
el('f-loader').addEventListener('change', () => updateLoaderVersions());
|
||||
el('f-version').addEventListener('change', () => updateLoaderVersions());
|
||||
|
||||
el('f-optifine-install').addEventListener('click', async () => {
|
||||
if (!editingId) return;
|
||||
const btn = el('f-optifine-install');
|
||||
const status = el('f-optifine-status');
|
||||
btn.disabled = true;
|
||||
status.textContent = 'Minecraft wird vorbereitet …';
|
||||
try {
|
||||
const res = await window.api.optifineSetup(editingId);
|
||||
if (res.canceled) { status.textContent = ''; return; }
|
||||
if (!res.ok) { status.textContent = '✖ ' + (res.message || 'Fehlgeschlagen.'); toast(res.message || 'OptiFine-Installer konnte nicht gestartet werden.', true); return; }
|
||||
status.textContent = `Installer gestartet – Zielordner wurde in die Zwischenablage kopiert: ${res.sharedDir} – `
|
||||
+ 'im OptiFine-Fenster als Minecraft-Ordner einfügen, „Install" klicken, danach hier bestätigen.';
|
||||
el('f-optifine-confirm').classList.remove('hidden');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
el('f-optifine-confirm').addEventListener('click', async () => {
|
||||
if (!editingId) return;
|
||||
const btn = el('f-optifine-confirm');
|
||||
const status = el('f-optifine-status');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await window.api.optifineConfirm(editingId);
|
||||
if (!res.ok) { status.textContent = '✖ ' + (res.message || 'Nicht gefunden.'); toast(res.message || 'OptiFine-Version wurde nicht gefunden.', true); return; }
|
||||
toast('OptiFine ' + res.versionId + ' übernommen.');
|
||||
status.textContent = '✔ Übernommen: ' + res.versionId;
|
||||
const sel = el('f-loader-version');
|
||||
if (![...sel.options].some((o) => o.value === res.versionId)) {
|
||||
const o = document.createElement('option');
|
||||
o.value = res.versionId; o.textContent = res.versionId;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
sel.value = res.versionId;
|
||||
btn.classList.add('hidden');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
el('f-pick-image').addEventListener('click', async () => {
|
||||
const p = await window.api.pickImage();
|
||||
if (!p) return;
|
||||
|
||||
Reference in New Issue
Block a user