Upload via GUI (28 Dateien)
This commit is contained in:
+323
-15
@@ -620,6 +620,99 @@ async function runModpackImport() {
|
||||
}
|
||||
}
|
||||
|
||||
function fmtDownloads(n) {
|
||||
n = Number(n) || 0;
|
||||
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
|
||||
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
return String(n);
|
||||
}
|
||||
|
||||
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 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',
|
||||
});
|
||||
if (res.error) {
|
||||
st.textContent = 'Fehler: ' + res.error;
|
||||
box.innerHTML = '<div class="mods-empty">Suche fehlgeschlagen.</div>';
|
||||
return;
|
||||
}
|
||||
const hits = res.hits || [];
|
||||
st.textContent = hits.length
|
||||
? (res.total + ' Treffer' + (gameVersion ? ' für ' + gameVersion : ''))
|
||||
: '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';
|
||||
const meta = [
|
||||
h.author ? ('von ' + h.author) : '',
|
||||
h.downloads != null ? (fmtDownloads(h.downloads) + ' Downloads') : '',
|
||||
].filter(Boolean).join(' · ');
|
||||
const cats = (h.displayCategories || h.categories || []).slice(0, 4).join(', ');
|
||||
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.title || h.slug || h.projectId)}</div>
|
||||
<div class="bd-meta">${escapeHtml(meta)}${cats ? ' · ' + escapeHtml(cats) : ''}</div>
|
||||
<div class="bd-meta" style="opacity:.85">${escapeHtml((h.description || '').slice(0, 120))}${(h.description || '').length > 120 ? '…' : ''}</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', () => installModpackFromSearch(h, btn));
|
||||
item.querySelector('.mod-action').appendChild(btn);
|
||||
box.appendChild(item);
|
||||
}
|
||||
} catch (err) {
|
||||
st.textContent = 'Fehler: ' + (err.message || err);
|
||||
box.innerHTML = '<div class="mods-empty">Suche fehlgeschlagen.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function installModpackFromSearch(hit, btn) {
|
||||
if (!hit || !hit.projectId) return;
|
||||
const gameVersion = el('mp-mc').value.trim();
|
||||
const prev = btn ? btn.textContent : '';
|
||||
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,
|
||||
});
|
||||
status('Bereit'); el('status-right').textContent = '';
|
||||
if (res.ok) {
|
||||
await reload();
|
||||
hide('modal-import');
|
||||
toast(`Modpack installiert: ${res.instance.name} (${res.fileCount || 0} Dateien)`);
|
||||
if (currentDetailId !== res.instance.id) {
|
||||
try { await selectInstance(res.instance.id); } catch { /* egal */ }
|
||||
}
|
||||
return;
|
||||
}
|
||||
toast(res.message || 'Installation fehlgeschlagen.', true);
|
||||
} catch (err) {
|
||||
status('Bereit'); el('status-right').textContent = '';
|
||||
toast('Installation fehlgeschlagen: ' + (err.message || err), true);
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = prev || 'Installieren'; }
|
||||
}
|
||||
}
|
||||
|
||||
async function scanMc() {
|
||||
const folder = el('mc-path').value.trim();
|
||||
el('mc-status').textContent = 'Suche Launcher-Profile …';
|
||||
@@ -1689,14 +1782,14 @@ async function selectInstance(id) {
|
||||
|
||||
let launching = false;
|
||||
|
||||
async function launch(id, accountId) {
|
||||
async function launch(id, accountId, launchOpts) {
|
||||
if (launching) return;
|
||||
launching = true;
|
||||
status('Vorbereiten …');
|
||||
el('status-right').textContent = '';
|
||||
appendConsole('\n=== Start: ' + new Date().toLocaleTimeString('de-DE') + ' ===\n');
|
||||
try {
|
||||
const res = await window.api.launchInstance(id, accountId);
|
||||
const res = await window.api.launchInstance(id, accountId, launchOpts || null);
|
||||
if (res.ok) {
|
||||
status('Läuft (PID ' + res.pid + ')');
|
||||
toast('Minecraft gestartet.');
|
||||
@@ -1847,6 +1940,37 @@ async function renderServers() {
|
||||
<div class="mod-action"></div>`;
|
||||
const aktionen = item.querySelector('.mod-action');
|
||||
|
||||
const starten = document.createElement('button');
|
||||
starten.className = 'btn btn-primary btn-mini';
|
||||
starten.textContent = 'Starten';
|
||||
starten.title = 'Instanz starten und direkt diesem Server beitreten (Quick Play, ab MC 1.20)';
|
||||
starten.addEventListener('click', () => {
|
||||
hide('modal-servers');
|
||||
launch(currentDetailId, null, { quickPlayServer: s.ip });
|
||||
});
|
||||
aktionen.appendChild(starten);
|
||||
|
||||
const umbenennen = document.createElement('button');
|
||||
umbenennen.className = 'btn btn-mini';
|
||||
umbenennen.textContent = 'Umbenennen';
|
||||
umbenennen.title = 'Anzeigenamen ändern';
|
||||
umbenennen.addEventListener('click', async () => {
|
||||
const aktuell = s.name || s.ip;
|
||||
const eingabe = await askPrompt({
|
||||
title: 'Server umbenennen',
|
||||
text: 'Adresse: ' + s.ip,
|
||||
label: 'Name',
|
||||
value: aktuell,
|
||||
okLabel: 'Speichern',
|
||||
});
|
||||
if (eingabe == null) return; // abgebrochen
|
||||
const res = await window.api.serversRename(currentDetailId, s.ip, eingabe);
|
||||
if (!res.ok) { toast(res.message || 'Umbenennen fehlgeschlagen.', true); return; }
|
||||
if (!res.unveraendert) toast('Umbenannt: ' + eingabe.trim());
|
||||
await renderServers();
|
||||
});
|
||||
aktionen.appendChild(umbenennen);
|
||||
|
||||
const kopieren = document.createElement('button');
|
||||
kopieren.className = 'btn btn-mini';
|
||||
kopieren.textContent = 'Übertragen…';
|
||||
@@ -2184,34 +2308,80 @@ function openConsole() {
|
||||
// ---------- Welten & Ressourcenpakete ----------
|
||||
async function openContent() {
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
el('content-title').textContent = 'Welten & Ressourcenpakete – ' + inst.name;
|
||||
el('content-title').textContent = 'Welten, Ressourcen- & Datenpakete – ' + inst.name;
|
||||
await renderContentLists();
|
||||
show('modal-content');
|
||||
}
|
||||
async function renderContentLists() {
|
||||
renderContentList('content-worlds', await window.api.worlds(currentDetailId), 'saves', '🌍');
|
||||
renderContentList('content-rp', await window.api.resourcepacks(currentDetailId), 'resourcepacks', '🎨');
|
||||
renderContentList('content-dp', await window.api.datapacks(currentDetailId), 'datapacks', '📜');
|
||||
}
|
||||
// ---------- Screenshots ----------
|
||||
let screenSelected = new Set();
|
||||
|
||||
function updateScreensSelectionUi() {
|
||||
const n = screenSelected.size;
|
||||
const btn = el('screens-delete-sel');
|
||||
if (btn) {
|
||||
btn.disabled = n === 0;
|
||||
btn.textContent = n ? (n + ' löschen') : 'Auswahl löschen';
|
||||
}
|
||||
const hint = el('screens-sel-hint');
|
||||
if (hint) hint.textContent = n ? (n + ' ausgewählt – Doppelklick öffnet das Bild') : 'Klick auf Vorschaubild zum Auswählen · Doppelklick öffnet';
|
||||
document.querySelectorAll('#screens-grid .screen-thumb').forEach((t) => {
|
||||
const name = t.dataset.name;
|
||||
const on = name && screenSelected.has(name);
|
||||
t.classList.toggle('selected', !!on);
|
||||
const cb = t.querySelector('.screen-check');
|
||||
if (cb) cb.checked = !!on;
|
||||
});
|
||||
}
|
||||
|
||||
async function openScreens() {
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
el('screens-title').textContent = 'Screenshots – ' + inst.name;
|
||||
const grid = el('screens-grid');
|
||||
grid.innerHTML = '<div class="screens-empty">Lade …</div>';
|
||||
screenSelected = new Set();
|
||||
updateScreensSelectionUi();
|
||||
show('modal-screens');
|
||||
|
||||
const shots = await window.api.screenshots(currentDetailId);
|
||||
grid.innerHTML = '';
|
||||
if (!shots.length) { grid.innerHTML = '<div class="screens-empty">Keine Screenshots vorhanden.</div>'; return; }
|
||||
if (!shots.length) {
|
||||
grid.innerHTML = '<div class="screens-empty">Keine Screenshots vorhanden.</div>';
|
||||
updateScreensSelectionUi();
|
||||
return;
|
||||
}
|
||||
for (const s of shots) {
|
||||
const thumb = document.createElement('div');
|
||||
thumb.className = 'screen-thumb';
|
||||
thumb.dataset.name = s.name;
|
||||
thumb.innerHTML =
|
||||
`<img alt="" /><div class="screen-name">${escapeHtml(s.name)}</div>
|
||||
`<input type="checkbox" class="screen-check" title="Auswählen" />
|
||||
<img alt="" /><div class="screen-name">${escapeHtml(s.name)}</div>
|
||||
<button class="screen-del" title="Löschen">🗑</button>`;
|
||||
const img = thumb.querySelector('img');
|
||||
window.api.screenshotData(currentDetailId, s.name).then((d) => { if (d) img.src = d; });
|
||||
img.addEventListener('click', () => window.api.openScreenshot(currentDetailId, s.name));
|
||||
|
||||
const toggle = () => {
|
||||
if (screenSelected.has(s.name)) screenSelected.delete(s.name);
|
||||
else screenSelected.add(s.name);
|
||||
updateScreensSelectionUi();
|
||||
};
|
||||
thumb.querySelector('.screen-check').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggle();
|
||||
});
|
||||
thumb.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.screen-del') || e.target.closest('.screen-check')) return;
|
||||
toggle();
|
||||
});
|
||||
img.addEventListener('dblclick', (e) => {
|
||||
e.stopPropagation();
|
||||
window.api.openScreenshot(currentDetailId, s.name);
|
||||
});
|
||||
thumb.querySelector('.screen-del').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
askConfirm('Screenshot löschen', `„${s.name}" löschen?`, '🗑 Löschen', async () => {
|
||||
@@ -2221,6 +2391,22 @@ async function openScreens() {
|
||||
});
|
||||
grid.appendChild(thumb);
|
||||
}
|
||||
updateScreensSelectionUi();
|
||||
}
|
||||
|
||||
async function deleteSelectedScreenshots() {
|
||||
const names = [...screenSelected];
|
||||
if (!names.length || !currentDetailId) return;
|
||||
const label = names.length === 1
|
||||
? `„${names[0]}" löschen?`
|
||||
: `${names.length} Screenshots unwiderruflich löschen?`;
|
||||
askConfirm('Screenshots löschen', label, '🗑 Löschen', async () => {
|
||||
for (const name of names) {
|
||||
await window.api.deleteContent(currentDetailId, 'screenshots', name);
|
||||
}
|
||||
toast(names.length === 1 ? 'Screenshot gelöscht.' : names.length + ' Screenshots gelöscht.');
|
||||
openScreens();
|
||||
});
|
||||
}
|
||||
|
||||
function renderContentList(boxId, items, sub, emoji) {
|
||||
@@ -2457,28 +2643,52 @@ async function loadCatalog(append) {
|
||||
if (ART_BRAUCHT_LOADER[modArt] && inst.minecraft.loader === 'vanilla') return;
|
||||
const box = el('m-results');
|
||||
if (!append) { modOffset = 0; box.innerHTML = '<div class="mods-empty">Lade …</div>'; el('m-more').classList.add('hidden'); }
|
||||
const source = (el('m-source') && el('m-source').value) || 'modrinth';
|
||||
if (el('m-category')) el('m-category').disabled = source === 'curseforge' || modArt !== 'mod';
|
||||
try {
|
||||
const res = await window.api.modSearch({
|
||||
query: el('m-query').value.trim(),
|
||||
gameVersion: inst.minecraft.version,
|
||||
loader: inst.minecraft.loader,
|
||||
category: modArt === 'mod' ? el('m-category').value : '',
|
||||
category: (source === 'modrinth' && modArt === 'mod') ? el('m-category').value : '',
|
||||
sort: el('m-sort').value,
|
||||
limit: 30,
|
||||
offset: modOffset,
|
||||
art: modArt,
|
||||
source,
|
||||
});
|
||||
if (!append) box.innerHTML = '';
|
||||
if (!res.hits.length && !append) {
|
||||
if (res && (res.error || res.needKey)) {
|
||||
if (!append) {
|
||||
box.innerHTML = `<div class="mods-empty">${escapeHtml(res.error || 'Suche nicht möglich.')}</div>`;
|
||||
if (res.needKey || /API-Key|Weiterleitung reicht/i.test(res.error || '')) {
|
||||
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-mods'); openSettings('pane-network'); });
|
||||
wrap.appendChild(btn);
|
||||
box.appendChild(wrap);
|
||||
}
|
||||
}
|
||||
el('m-more').classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
const hits = (res && res.hits) || [];
|
||||
if (!hits.length && !append) {
|
||||
box.innerHTML = '<div class="mods-empty">Nichts gefunden.</div>';
|
||||
el('m-more').classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
for (const mod of res.hits) box.appendChild(buildResultItem(mod));
|
||||
modOffset += res.hits.length;
|
||||
el('m-more').classList.toggle('hidden', res.hits.length < 30 || modOffset >= res.total);
|
||||
for (const mod of hits) box.appendChild(buildResultItem(mod));
|
||||
modOffset += hits.length;
|
||||
el('m-more').classList.toggle('hidden', hits.length < 30 || modOffset >= (res.total || 0));
|
||||
} catch (e) {
|
||||
if (!append) box.innerHTML = `<div class="mods-empty">Fehler: ${escapeHtml(e.message)}</div>`;
|
||||
if (!append) {
|
||||
const msg = (e && e.message) ? e.message : String(e);
|
||||
box.innerHTML = `<div class="mods-empty">Fehler: ${escapeHtml(msg)}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2502,7 +2712,7 @@ function buildResultItem(mod) {
|
||||
info.innerHTML =
|
||||
`<div class="mod-name">${escapeHtml(mod.title)}</div>
|
||||
<div class="mod-desc">${escapeHtml(mod.description || '')}</div>
|
||||
<div class="mod-meta">von ${escapeHtml(mod.author)} · ${Number(mod.downloads).toLocaleString('de-DE')} Downloads</div>`;
|
||||
<div class="mod-meta">von ${escapeHtml(mod.author || 'unbekannt')} · ${Number(mod.downloads).toLocaleString('de-DE')} Downloads${mod.source === 'curseforge' ? ' · CurseForge' : ''}</div>`;
|
||||
|
||||
const action = document.createElement('div');
|
||||
action.className = 'mod-action';
|
||||
@@ -3119,6 +3329,31 @@ function onAuthCode(code) {
|
||||
}
|
||||
|
||||
// ---------- Bestätigung ----------
|
||||
|
||||
let promptResolver = null;
|
||||
function askPrompt({ title, text, label, value, okLabel } = {}) {
|
||||
return new Promise((resolve) => {
|
||||
// falls schon ein Prompt offen ist
|
||||
if (promptResolver) { promptResolver(null); promptResolver = null; }
|
||||
promptResolver = resolve;
|
||||
el('prompt-title').textContent = title || 'Eingabe';
|
||||
el('prompt-text').textContent = text || '';
|
||||
el('prompt-text').classList.toggle('hidden', !text);
|
||||
el('prompt-label').textContent = label || 'Wert';
|
||||
el('prompt-ok').textContent = okLabel || 'OK';
|
||||
const input = el('prompt-input');
|
||||
input.value = value != null ? String(value) : '';
|
||||
show('modal-prompt');
|
||||
setTimeout(() => { input.focus(); input.select(); }, 30);
|
||||
});
|
||||
}
|
||||
function closePrompt(wert) {
|
||||
hide('modal-prompt');
|
||||
const r = promptResolver;
|
||||
promptResolver = null;
|
||||
if (r) r(wert);
|
||||
}
|
||||
|
||||
function askConfirm(title, text, okLabel, action) {
|
||||
el('confirm-title').textContent = title;
|
||||
el('confirm-text').textContent = text;
|
||||
@@ -3189,10 +3424,20 @@ function wire() {
|
||||
tab.addEventListener('click', async () => {
|
||||
setImportKind(tab.dataset.kind);
|
||||
if (tab.dataset.kind === 'mc') await ensureDefaultMcScan();
|
||||
if (tab.dataset.kind === 'modpack') {
|
||||
const box = el('mp-results');
|
||||
if (box && !box.dataset.loaded) {
|
||||
box.dataset.loaded = '1';
|
||||
searchModpacks();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
el('import-zip-do').addEventListener('click', runZipImport);
|
||||
el('import-modpack-do').addEventListener('click', runModpackImport);
|
||||
el('mp-search-btn').addEventListener('click', searchModpacks);
|
||||
el('mp-query').addEventListener('keydown', (e) => { if (e.key === 'Enter') searchModpacks(); });
|
||||
el('mp-mc').addEventListener('keydown', (e) => { if (e.key === 'Enter') searchModpacks(); });
|
||||
el('mc-pick').addEventListener('click', async () => {
|
||||
const p = await window.api.mcPickFolder();
|
||||
if (p) { el('mc-path').value = p; await scanMc(); }
|
||||
@@ -3375,12 +3620,25 @@ function wire() {
|
||||
el('content-open-worlds').addEventListener('click', () => window.api.openSubdir(currentDetailId, 'saves'));
|
||||
el('content-open-rp').addEventListener('click', () => window.api.openSubdir(currentDetailId, 'resourcepacks'));
|
||||
el('screens-open').addEventListener('click', () => window.api.openSubdir(currentDetailId, 'screenshots'));
|
||||
el('screens-select-all').addEventListener('click', () => {
|
||||
document.querySelectorAll('#screens-grid .screen-thumb').forEach((t) => {
|
||||
if (t.dataset.name) screenSelected.add(t.dataset.name);
|
||||
});
|
||||
updateScreensSelectionUi();
|
||||
});
|
||||
el('screens-select-none').addEventListener('click', () => {
|
||||
screenSelected.clear();
|
||||
updateScreensSelectionUi();
|
||||
});
|
||||
el('screens-delete-sel').addEventListener('click', () => deleteSelectedScreenshots());
|
||||
el('content-open-dp').addEventListener('click', () => window.api.openSubdir(currentDetailId, 'datapacks'));
|
||||
|
||||
// Mod-Dialog
|
||||
el('m-search-btn').addEventListener('click', () => loadCatalog(false));
|
||||
el('m-query').addEventListener('keydown', (e) => { if (e.key === 'Enter') loadCatalog(false); });
|
||||
el('m-sort').addEventListener('change', () => loadCatalog(false));
|
||||
el('m-category').addEventListener('change', () => loadCatalog(false));
|
||||
if (el('m-source')) el('m-source').addEventListener('change', () => loadCatalog(false));
|
||||
el('m-more').addEventListener('click', () => loadCatalog(true));
|
||||
el('m-tab-catalog').addEventListener('click', () => switchModTab('catalog'));
|
||||
el('m-tab-installed').addEventListener('click', () => switchModTab('installed'));
|
||||
@@ -3447,6 +3705,25 @@ function wire() {
|
||||
el('s-backup-global').addEventListener('click', runGlobalBackup);
|
||||
el('s-restore-global').addEventListener('click', promptGlobalRestore);
|
||||
el('btn-save-settings').addEventListener('click', saveSettings);
|
||||
if (el('s-cf-test')) {
|
||||
el('s-cf-test').addEventListener('click', async () => {
|
||||
const st = el('s-cf-test-status');
|
||||
const key = el('s-cf-key').value.trim();
|
||||
st.textContent = 'Prüfe bei CurseForge …';
|
||||
try {
|
||||
const res = await window.api.curseforgeTestKey(key);
|
||||
st.textContent = (res.ok ? '✓ ' : '✖ ') + (res.message || '');
|
||||
if (res.proben) {
|
||||
st.textContent += ' [' + res.proben.map((p) => p.name + ':' + p.status).join(', ') + ']';
|
||||
}
|
||||
if (res.siehtAusWieCurseForge === false && key) {
|
||||
st.textContent += ' Hinweis: Erwartet wird ein Studios-Key (cfc_pat_…) oder ein klassischer Key ($2a$10$…).';
|
||||
}
|
||||
} catch (err) {
|
||||
st.textContent = '✖ ' + (err.message || err);
|
||||
}
|
||||
});
|
||||
}
|
||||
document.querySelectorAll('.snav').forEach((b) => b.addEventListener('click', () => setSettingsPane(b.dataset.pane)));
|
||||
el('s-open-accounts').addEventListener('click', () => { hide('modal-settings'); openLogin(); });
|
||||
el('s-add-account').addEventListener('click', () => { hide('modal-settings'); openLogin().then(addAccount); });
|
||||
@@ -3484,6 +3761,27 @@ function wire() {
|
||||
});
|
||||
|
||||
// Bestätigung
|
||||
el('prompt-ok').addEventListener('click', () => {
|
||||
const v = el('prompt-input').value;
|
||||
closePrompt(v);
|
||||
});
|
||||
el('prompt-input').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
closePrompt(el('prompt-input').value);
|
||||
}
|
||||
});
|
||||
// Abbrechen / Schließen des Prompt-Dialogs liefert null
|
||||
el('modal-prompt').querySelectorAll('[data-close]').forEach((b) => {
|
||||
b.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
closePrompt(null);
|
||||
});
|
||||
});
|
||||
el('modal-prompt').addEventListener('mousedown', (e) => {
|
||||
if (e.target === el('modal-prompt')) closePrompt(null);
|
||||
});
|
||||
|
||||
el('confirm-ok').addEventListener('click', async () => {
|
||||
hide('modal-confirm');
|
||||
if (confirmAction) { const a = confirmAction; confirmAction = null; await a(); }
|
||||
@@ -3491,12 +3789,22 @@ function wire() {
|
||||
|
||||
// generisches Schließen
|
||||
document.querySelectorAll('[data-close]').forEach((b) =>
|
||||
b.addEventListener('click', () => b.closest('.modal').classList.add('hidden')));
|
||||
b.addEventListener('click', () => {
|
||||
const m = b.closest('.modal');
|
||||
if (!m) return;
|
||||
if (m.id === 'modal-prompt') { closePrompt(null); return; }
|
||||
m.classList.add('hidden');
|
||||
}));
|
||||
document.querySelectorAll('.modal').forEach((m) =>
|
||||
m.addEventListener('mousedown', (e) => { if (e.target === m) m.classList.add('hidden'); }));
|
||||
m.addEventListener('mousedown', (e) => {
|
||||
if (e.target !== m) return;
|
||||
if (m.id === 'modal-prompt') { closePrompt(null); return; }
|
||||
m.classList.add('hidden');
|
||||
}));
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (!el('ctx-menu').classList.contains('hidden')) { closeContextMenu(); return; }
|
||||
if (!el('modal-prompt').classList.contains('hidden')) { closePrompt(null); return; }
|
||||
const open = [...document.querySelectorAll('.modal:not(.hidden)')].pop();
|
||||
if (open) open.classList.add('hidden');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user