Upload via GUI (45 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-08 22:09:57 +02:00
parent 33e533009e
commit 87fded4cb6
10 changed files with 426 additions and 28 deletions
+132 -15
View File
@@ -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;