Upload via GUI (123 Dateien)
This commit is contained in:
+146
-8
@@ -537,13 +537,23 @@ function escapeHtml(s) {
|
||||
}
|
||||
function numOrNull(v) { const n = parseInt(v, 10); return Number.isFinite(n) ? n : null; }
|
||||
|
||||
// Designs: 'dark'/'light' = Klassisch (MultiMC-Look), 'aero' = wie die Webseite,
|
||||
// 'lila' = Aero in dunklem Lila, 'minecraft' = Block-Optik. Alle außer Klassisch
|
||||
// bauen auf den dunklen Regeln auf (data-theme="dark") und überschreiben sie über data-design.
|
||||
const DESIGNS = ['dark', 'light', 'aero', 'lila', 'minecraft'];
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme === 'light' ? 'light' : 'dark');
|
||||
const t = DESIGNS.includes(theme) ? theme : 'dark';
|
||||
const root = document.documentElement;
|
||||
root.setAttribute('data-theme', t === 'light' ? 'light' : 'dark');
|
||||
root.setAttribute('data-design', ['aero', 'lila', 'minecraft'].includes(t) ? t : 'klassisch');
|
||||
if (!POPOUT && window.api.setTitleBarDesign) window.api.setTitleBarDesign(t);
|
||||
document.querySelectorAll('.design-wahl').forEach((b) => b.classList.toggle('on', b.dataset.design === t));
|
||||
}
|
||||
|
||||
// ---------- Instanzen aus MultiMC übernehmen ----------
|
||||
let mmcFound = [];
|
||||
let cfFound = [];
|
||||
let mrFound = [];
|
||||
let mcFound = [];
|
||||
let technicFound = [];
|
||||
let importKind = 'zip';
|
||||
@@ -568,8 +578,8 @@ async function ensureDefaultMcScan() {
|
||||
}
|
||||
|
||||
function setImportKind(kind) {
|
||||
importKind = ['zip', 'modpack', 'mc', 'cf', 'mmc', 'technic', 'code'].includes(kind) ? kind : 'zip';
|
||||
['zip', 'modpack', 'mc', 'cf', 'mmc', 'technic', 'code'].forEach((entryKind) => {
|
||||
importKind = ['zip', 'modpack', 'mc', 'cf', 'mr', 'mmc', 'technic', 'code'].includes(kind) ? kind : 'zip';
|
||||
['zip', 'modpack', 'mc', 'cf', 'mr', 'mmc', 'technic', 'code'].forEach((entryKind) => {
|
||||
el(`import-tab-${entryKind}`).classList.toggle('on', entryKind === importKind);
|
||||
el(`import-pane-${entryKind}`).classList.toggle('hidden', entryKind !== importKind);
|
||||
});
|
||||
@@ -577,12 +587,14 @@ function setImportKind(kind) {
|
||||
el('import-modpack-do').classList.toggle('hidden', importKind !== 'modpack');
|
||||
el('mc-do').classList.toggle('hidden', importKind !== 'mc');
|
||||
el('cf-do').classList.toggle('hidden', importKind !== 'cf');
|
||||
el('mr-do').classList.toggle('hidden', importKind !== 'mr');
|
||||
el('mmc-do').classList.toggle('hidden', importKind !== 'mmc');
|
||||
el('technic-do').classList.toggle('hidden', importKind !== 'technic');
|
||||
el('code-do').classList.toggle('hidden', importKind !== 'code');
|
||||
el('code-progress').classList.toggle('hidden', importKind !== 'code' || !el('code-progress').textContent);
|
||||
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('mr-progress').classList.toggle('hidden', importKind !== 'mr' || !el('mr-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);
|
||||
}
|
||||
@@ -601,6 +613,12 @@ function openImportDialog(kind = 'zip', vorauswahl = null) {
|
||||
el('cf-progress').textContent = '';
|
||||
el('cf-progress').classList.add('hidden');
|
||||
el('cf-status').textContent = 'Standardpfad unter Windows: C:\\Users\\DeinName\\curseforge\\minecraft\\Instances';
|
||||
mrFound = [];
|
||||
el('mr-result').classList.add('hidden');
|
||||
el('mr-do').disabled = true;
|
||||
el('mr-progress').textContent = '';
|
||||
el('mr-progress').classList.add('hidden');
|
||||
el('mr-status').textContent = 'Leer lassen nutzt automatisch den Standardort der Modrinth App.';
|
||||
el('mmc-result').classList.add('hidden');
|
||||
el('mmc-do').disabled = true;
|
||||
el('mmc-progress').textContent = '';
|
||||
@@ -613,6 +631,8 @@ function openImportDialog(kind = 'zip', vorauswahl = null) {
|
||||
ensureDefaultMcScan();
|
||||
}
|
||||
if (importKind === 'cf') el('cf-path').focus();
|
||||
// Modrinth App: Standardort gleich durchsuchen, wie beim Minecraft Launcher
|
||||
if (importKind === 'mr' && !(Array.isArray(vorauswahl) && vorauswahl.length)) scanMr();
|
||||
if (importKind === 'mmc') el('mmc-path').focus();
|
||||
if (Array.isArray(vorauswahl) && vorauswahl.length) uebernehmeSetupVorauswahl(vorauswahl);
|
||||
}
|
||||
@@ -622,7 +642,7 @@ function openImportDialog(kind = 'zip', vorauswahl = null) {
|
||||
* eintragen und durchsuchen, damit man beim ersten Start nur noch auswählt.
|
||||
*/
|
||||
function uebernehmeSetupVorauswahl(quellen) {
|
||||
const scanner = { mc: scanMc, cf: scanCf, mmc: scanMmc, technic: scanTechnic };
|
||||
const scanner = { mc: scanMc, cf: scanCf, mr: scanMr, mmc: scanMmc, technic: scanTechnic };
|
||||
importWarteschlange = quellen.map((q) => q && q.kind).filter((k) => scanner[k]);
|
||||
for (const q of quellen) {
|
||||
if (!q || !scanner[q.kind]) continue;
|
||||
@@ -923,6 +943,82 @@ async function runCfImport() {
|
||||
if (instances.length) await selectInstance(instances[0].id);
|
||||
}
|
||||
|
||||
async function scanMr() {
|
||||
const folder = el('mr-path').value.trim();
|
||||
el('mr-status').textContent = 'Suche Instanzen der Modrinth App …';
|
||||
const res = await window.api.mrScan(folder);
|
||||
if (!res.ok) {
|
||||
el('mr-status').textContent = folder
|
||||
? '✖ Dort wurden keine Daten der Modrinth App gefunden. Gemeint ist der Ordner mit „app.db“ und „profiles“.'
|
||||
: '✖ Die Modrinth App wurde am Standardort nicht gefunden. Ist sie installiert? Sonst den Datenordner wählen.';
|
||||
el('mr-result').classList.add('hidden');
|
||||
el('mr-do').disabled = true;
|
||||
return;
|
||||
}
|
||||
mrFound = res.instances;
|
||||
el('mr-status').textContent = `${res.count} Instanz${res.count === 1 ? '' : 'en'} gefunden (${res.basis}).`
|
||||
+ (res.hinweis ? ' ⚠ ' + res.hinweis : '');
|
||||
el('mr-result').classList.remove('hidden');
|
||||
el('mr-all').checked = true;
|
||||
renderMrList();
|
||||
}
|
||||
|
||||
function renderMrList() {
|
||||
const box = el('mr-list');
|
||||
box.innerHTML = '';
|
||||
if (!mrFound.length) { box.innerHTML = '<div class="mods-empty">Keine Instanzen gefunden.</div>'; return; }
|
||||
mrFound.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 || 'ohne Version', loaderTxt, inst.group ? 'Gruppe: ' + inst.group : '',
|
||||
inst.modpack ? 'Modrinth-Modpack' : '', inst.playSeconds ? fmtDuration(inst.playSeconds) + ' gespielt' : '',
|
||||
inst.installiert === false ? 'nicht fertig installiert' : '']
|
||||
.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);
|
||||
});
|
||||
updateMrButton();
|
||||
}
|
||||
|
||||
function selectedMr() {
|
||||
return [...document.querySelectorAll('#mr-list input[type=checkbox]')]
|
||||
.filter((c) => c.checked)
|
||||
.map((c) => mrFound[Number(c.dataset.idx)]);
|
||||
}
|
||||
|
||||
function updateMrButton() {
|
||||
const n = selectedMr().length;
|
||||
el('mr-do').disabled = n === 0;
|
||||
el('mr-do').textContent = n ? `${n} übernehmen` : 'Übernehmen';
|
||||
}
|
||||
|
||||
async function runMrImport() {
|
||||
const entries = selectedMr();
|
||||
if (!entries.length) return;
|
||||
const copy = el('mr-copy').checked;
|
||||
el('mr-do').disabled = true;
|
||||
el('mr-progress').textContent = 'Übernehme …';
|
||||
el('mr-progress').classList.remove('hidden');
|
||||
|
||||
const res = await window.api.mrImport(entries, copy);
|
||||
await reload();
|
||||
importAbgeschlossen('mr');
|
||||
const n = res.imported.length;
|
||||
const f = res.failed.length;
|
||||
const ohneLink = res.imported.filter((i) => i.modpack === false).length;
|
||||
toast(`${n} Instanz${n === 1 ? '' : 'en'} aus der Modrinth App übernommen${f ? ` · ${f} fehlgeschlagen` : ''}`
|
||||
+ (ohneLink ? ` · ${ohneLink}× Modpack-Verknüpfung nicht möglich (offline?)` : '') + '.', f > 0);
|
||||
if (instances.length) await selectInstance(instances[0].id);
|
||||
}
|
||||
|
||||
async function scanMmc() {
|
||||
const folder = el('mmc-path').value.trim();
|
||||
if (!folder) { el('mmc-status').textContent = 'Bitte zuerst einen Ordner angeben.'; return; }
|
||||
@@ -1464,7 +1560,11 @@ async function applySkinBackground(uuidOverride) {
|
||||
// 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(`https://crafthead.net/skin/${uuid}`, s.bgSkinPose || 'walking');
|
||||
// Armform: fest gewählt oder (Standard) die des Kontos – sonst sieht ein
|
||||
// schmaler Alex-Skin mit breiten Armen falsch aus (grauer Streifen am Arm)
|
||||
let modell = s.bgSkinModel === 'slim' || s.bgSkinModel === 'classic' ? s.bgSkinModel : '';
|
||||
if (!modell) { try { modell = await window.api.skinModelFor(uuid); } catch { modell = 'classic'; } }
|
||||
const local = await renderSkin3D(`https://crafthead.net/skin/${uuid}`, s.bgSkinPose || 'walking', { modell });
|
||||
if (local) { root.style.setProperty('--bg-skin', `url("${local}")`); return; }
|
||||
|
||||
// 2) Rückfall: externe Render-Dienste
|
||||
@@ -1630,7 +1730,7 @@ const ICONS_SVG = {
|
||||
terminal: '<path d="M4 5h16v14H4zM7 9l3 3-3 3M13 16h4"/>',
|
||||
user: '<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z"/>',
|
||||
users: '<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2M9 11a4 4 0 100-8 4 4 0 000 8zM23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/>',
|
||||
settings: '<circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/>',
|
||||
settings: '<path d="M19.1 13.1 21.5 14.5 20.5 16.9 17.8 16.3 16.3 17.8 16.9 20.5 14.5 21.5 13.1 19.1 10.9 19.1 9.5 21.5 7.1 20.5 7.7 17.8 6.2 16.3 3.5 16.9 2.5 14.5 4.9 13.1 4.9 10.9 2.5 9.5 3.5 7.1 6.2 7.7 7.7 6.2 7.1 3.5 9.5 2.5 10.9 4.9 13.1 4.9 14.5 2.5 16.9 3.5 16.3 6.2 17.8 7.7 20.5 7.1 21.5 9.5 19.1 10.9Z"/><circle cx="12" cy="12" r="3"/>', // Zahnrad (8 Zähne)
|
||||
play: '<path d="M7 5l12 7-12 7z"/>',
|
||||
blocks: '<path d="M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z"/>',
|
||||
globe: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 010 18M12 3a15 15 0 000 18"/>',
|
||||
@@ -4790,7 +4890,8 @@ async function openSettings(pane) {
|
||||
window.api.updateLastCheck().then(renderUpdateStatus).catch(() => {});
|
||||
|
||||
const s = await window.api.getSettings();
|
||||
el('s-theme').value = s.theme || 'dark';
|
||||
el('s-theme').value = DESIGNS.includes(s.theme) ? s.theme : 'dark';
|
||||
applyTheme(el('s-theme').value);
|
||||
el('s-global-pre').value = s.globalPreLaunch || '';
|
||||
el('s-global-post').value = s.globalPostExit || '';
|
||||
el('s-wrapper').value = s.wrapperCommand || '';
|
||||
@@ -4806,6 +4907,7 @@ async function openSettings(pane) {
|
||||
}
|
||||
sel.value = s.bgSkin || '';
|
||||
el('s-bgskin-pose').value = s.bgSkinPose || 'walking';
|
||||
el('s-bgskin-model').value = ['classic', 'slim'].includes(s.bgSkinModel) ? s.bgSkinModel : 'auto';
|
||||
el('s-bgskin-cycle').checked = !!s.bgSkinCycle;
|
||||
el('s-bgskin-cycle-seconds').value = s.bgSkinCycleSeconds || 30;
|
||||
sel.disabled = !!s.bgSkinCycle;
|
||||
@@ -4939,6 +5041,7 @@ async function saveSettings() {
|
||||
globalPostExit: el('s-global-post').value.trim(),
|
||||
bgSkin: el('s-bgskin').value,
|
||||
bgSkinPose: el('s-bgskin-pose').value,
|
||||
bgSkinModel: el('s-bgskin-model').value,
|
||||
bgSkinCycle: el('s-bgskin-cycle').checked,
|
||||
bgSkinCycleSeconds: Math.max(5, numOrNull(el('s-bgskin-cycle-seconds').value) || 30),
|
||||
sortBy: el('s-sortby').value,
|
||||
@@ -5471,6 +5574,9 @@ function wire() {
|
||||
});
|
||||
document.addEventListener('scroll', closeContextMenu, true);
|
||||
window.addEventListener('blur', closeContextMenu);
|
||||
// Titelleiste wie bei Windows abblenden, solange das Fenster nicht aktiv ist
|
||||
window.addEventListener('blur', () => document.body.classList.add('fenster-inaktiv'));
|
||||
window.addEventListener('focus', () => document.body.classList.remove('fenster-inaktiv'));
|
||||
|
||||
// Warnung anklicken -> sofort erneut prüfen
|
||||
el('status-auth').addEventListener('click', async () => {
|
||||
@@ -5530,6 +5636,18 @@ function wire() {
|
||||
});
|
||||
el('cf-list').addEventListener('change', updateCfButton);
|
||||
el('cf-do').addEventListener('click', runCfImport);
|
||||
el('mr-pick').addEventListener('click', async () => {
|
||||
const p = await window.api.mrPickFolder();
|
||||
if (p) { el('mr-path').value = p; await scanMr(); }
|
||||
});
|
||||
el('mr-scan').addEventListener('click', scanMr);
|
||||
el('mr-path').addEventListener('keydown', (e) => { if (e.key === 'Enter') scanMr(); });
|
||||
el('mr-all').addEventListener('change', (e) => {
|
||||
document.querySelectorAll('#mr-list input[type=checkbox]').forEach((c) => { c.checked = e.target.checked; });
|
||||
updateMrButton();
|
||||
});
|
||||
el('mr-list').addEventListener('change', updateMrButton);
|
||||
el('mr-do').addEventListener('click', runMrImport);
|
||||
el('mmc-pick').addEventListener('click', async () => {
|
||||
const p = await window.api.mmcPickFolder();
|
||||
if (p) { el('mmc-path').value = p; await scanMmc(); }
|
||||
@@ -5567,6 +5685,10 @@ function wire() {
|
||||
el('cf-progress').textContent = `Übernehme … ${p.current}/${p.total}`;
|
||||
el('cf-progress').classList.remove('hidden');
|
||||
});
|
||||
window.api.onMrProgress((p) => {
|
||||
el('mr-progress').textContent = p.detail ? `${p.detail} …` : `Übernehme … ${p.current}/${p.total}`;
|
||||
el('mr-progress').classList.remove('hidden');
|
||||
});
|
||||
window.api.onMmcProgress((p) => {
|
||||
el('mmc-progress').textContent = `Übernehme … ${p.current}/${p.total}`;
|
||||
el('mmc-progress').classList.remove('hidden');
|
||||
@@ -5804,6 +5926,10 @@ function wire() {
|
||||
el('skin-pick').addEventListener('click', waehleSkinDatei);
|
||||
el('skin-variante').addEventListener('change', skinVarianteGewechselt);
|
||||
el('skin-apply').addEventListener('click', setzeSkin);
|
||||
// gewählte Datei (sonst der aktuelle Skin des Kontos) im Editor öffnen
|
||||
el('skin-editor').addEventListener('click', () => window.openSkinEditor(gewaehlterSkin
|
||||
? { pfad: gewaehlterSkin, variante: el('skin-variante').value }
|
||||
: { quelle: 'aktuell' }));
|
||||
el('skin-reset').addEventListener('click', setzeSkinZurueck);
|
||||
el('logs-select').addEventListener('change', (e) => ladeLog(e.target.value));
|
||||
el('logs-folder').addEventListener('click', async () => {
|
||||
@@ -5986,6 +6112,11 @@ function wire() {
|
||||
if (p) { el('s-java').value = p; await refreshJavaStatus(p); }
|
||||
});
|
||||
el('s-theme').addEventListener('change', (e) => applyTheme(e.target.value));
|
||||
// Design-Kacheln setzen die (versteckte) Auswahl und zeigen es sofort an
|
||||
document.querySelectorAll('.design-wahl').forEach((b) => b.addEventListener('click', () => {
|
||||
el('s-theme').value = b.dataset.design;
|
||||
applyTheme(b.dataset.design);
|
||||
}));
|
||||
el('s-bgskin-opacity').addEventListener('input', (e) => {
|
||||
el('s-bgskin-val').textContent = e.target.value;
|
||||
document.documentElement.style.setProperty('--bg-skin-opacity', String(e.target.value / 100));
|
||||
@@ -5995,11 +6126,13 @@ function wire() {
|
||||
await window.api.saveSettings({
|
||||
bgSkin: el('s-bgskin').value,
|
||||
bgSkinPose: el('s-bgskin-pose').value,
|
||||
bgSkinModel: el('s-bgskin-model').value,
|
||||
});
|
||||
await refreshSkinBackground();
|
||||
};
|
||||
el('s-bgskin').addEventListener('change', previewSkin);
|
||||
el('s-bgskin-pose').addEventListener('change', previewSkin);
|
||||
el('s-bgskin-model').addEventListener('change', previewSkin);
|
||||
const previewSkinCycle = async () => {
|
||||
el('s-bgskin').disabled = el('s-bgskin-cycle').checked;
|
||||
await window.api.saveSettings({
|
||||
@@ -6240,6 +6373,7 @@ const POPOUT_OPENERS = {
|
||||
screens: () => openScreens(),
|
||||
servers: () => openServers(),
|
||||
skin: () => openSkin(),
|
||||
skineditor: (p) => window.openSkinEditor(p),
|
||||
bedrock: () => openBedrock(),
|
||||
login: (p) => openLogin({ addAccount: p.addAccount === true || p.addAccount === 'true' }),
|
||||
import: (p) => {
|
||||
@@ -6398,7 +6532,11 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
// Ein Skin-Wechsel oder eine neue/aktualisierte Instanz aus einem anderen
|
||||
// Fenster (Skin-/Import-Popup) betrifft ebenfalls nur die Anzeige im
|
||||
// Hauptfenster (Skin-Hintergrund, Seitenleiste) – Popups zeigen beides nicht an
|
||||
window.api.onSkinChanged(() => { if (!POPOUT) refreshSkinBackground(); });
|
||||
window.api.onSkinChanged((info) => {
|
||||
if (!POPOUT) refreshSkinBackground();
|
||||
// im Editor gesetzt -> offenes Skin-Fenster zeigt sonst noch den alten Stand
|
||||
if (POPOUT === 'skin' && info.quelle === 'editor' && !el('modal-skin').classList.contains('hidden')) openSkin();
|
||||
});
|
||||
window.api.onInstancesChanged(() => { if (!POPOUT) reload(); });
|
||||
try { showAuthHealth(await window.api.authHealth()); } catch { /* noch keine Prüfung */ }
|
||||
await loadMcIcons(); // echte Minecraft-Icons bereitstellen
|
||||
|
||||
Reference in New Issue
Block a user