Upload via GUI (50 Dateien)
This commit is contained in:
+355
-24
@@ -1507,6 +1507,7 @@ const ICONS_SVG = {
|
||||
folder: '<path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>',
|
||||
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"/>',
|
||||
play: '<path d="M7 5l12 7-12 7z"/>',
|
||||
blocks: '<path d="M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z"/>',
|
||||
@@ -2974,26 +2975,43 @@ function erzeugeSkaliertenStream(quellStream, zielBreite, zielHoehe, fps) {
|
||||
// unsichtbare Rahmen für die Größenänderung per Maus) - sonst wäre am Rand
|
||||
// ein dünner Streifen Desktop/Nachbarfenster im Zuschnitt zu sehen.
|
||||
const UNSICHTBARER_RAND = 8;
|
||||
const zeichnen = () => {
|
||||
if (video.readyState >= 2) {
|
||||
if (mcFensterBounds && mcBildschirmBounds && video.videoWidth && mcBildschirmBounds.width) {
|
||||
const skalX = video.videoWidth / mcBildschirmBounds.width;
|
||||
const skalY = video.videoHeight / mcBildschirmBounds.height;
|
||||
const randX = UNSICHTBARER_RAND * skalX;
|
||||
const randY = UNSICHTBARER_RAND * skalY;
|
||||
const sx = (mcFensterBounds.x - mcBildschirmBounds.x) * skalX + randX;
|
||||
const sy = (mcFensterBounds.y - mcBildschirmBounds.y) * skalY + randY;
|
||||
const sw = Math.max(1, mcFensterBounds.width * skalX - randX * 2);
|
||||
const sh = Math.max(1, mcFensterBounds.height * skalY - randY * 2);
|
||||
ctx.drawImage(video, sx, sy, sw, sh, 0, 0, zielBreite, zielHoehe);
|
||||
} else {
|
||||
ctx.drawImage(video, 0, 0, zielBreite, zielHoehe); // Übergang, bis Fenstergrenzen bekannt sind
|
||||
|
||||
// captureStream(fps) allein reicht nicht: requestAnimationFrame läuft mit
|
||||
// der Bildwiederholrate des Monitors (z. B. 144Hz), nicht mit der
|
||||
// gewünschten Aufnahme-Bildrate - ungebremst wurden dadurch weit mehr
|
||||
// Bilder gezeichnet/geliefert als angefragt (führte zu einer kaputten
|
||||
// Zeitachse im aufgenommenen Video und sichtbarem Ruckeln). Deshalb
|
||||
// captureStream(0) (kein automatisches Intervall) und Bilder nur exakt im
|
||||
// Takt der Ziel-Bildrate selbst per requestFrame() ausliefern.
|
||||
const canvasStream = canvas.captureStream(0);
|
||||
const spurAusgabe = canvasStream.getVideoTracks()[0];
|
||||
const intervallMs = 1000 / fps;
|
||||
let letzteZeichnung = 0;
|
||||
|
||||
const zeichnen = (jetzt) => {
|
||||
if (!letzteZeichnung || jetzt - letzteZeichnung >= intervallMs) {
|
||||
letzteZeichnung = jetzt;
|
||||
if (video.readyState >= 2) {
|
||||
if (mcFensterBounds && mcBildschirmBounds && video.videoWidth && mcBildschirmBounds.width) {
|
||||
const skalX = video.videoWidth / mcBildschirmBounds.width;
|
||||
const skalY = video.videoHeight / mcBildschirmBounds.height;
|
||||
const randX = UNSICHTBARER_RAND * skalX;
|
||||
const randY = UNSICHTBARER_RAND * skalY;
|
||||
const sx = (mcFensterBounds.x - mcBildschirmBounds.x) * skalX + randX;
|
||||
const sy = (mcFensterBounds.y - mcBildschirmBounds.y) * skalY + randY;
|
||||
const sw = Math.max(1, mcFensterBounds.width * skalX - randX * 2);
|
||||
const sh = Math.max(1, mcFensterBounds.height * skalY - randY * 2);
|
||||
ctx.drawImage(video, sx, sy, sw, sh, 0, 0, zielBreite, zielHoehe);
|
||||
} else {
|
||||
ctx.drawImage(video, 0, 0, zielBreite, zielHoehe); // Übergang, bis Fenstergrenzen bekannt sind
|
||||
}
|
||||
spurAusgabe.requestFrame();
|
||||
}
|
||||
}
|
||||
mcCanvasRAF = requestAnimationFrame(zeichnen);
|
||||
};
|
||||
zeichnen();
|
||||
return canvas.captureStream(fps);
|
||||
mcCanvasRAF = requestAnimationFrame(zeichnen);
|
||||
return canvasStream;
|
||||
}
|
||||
|
||||
async function erfasseFensterVideoMitCursor(source, grenzen) {
|
||||
@@ -3039,9 +3057,8 @@ async function wendeVideoEinstellungenAn() {
|
||||
|
||||
function updateVideoSummary() {
|
||||
const groesse = mcActualSize ? ` (${mcActualSize.width}×${mcActualSize.height})` : '';
|
||||
const cursorHinweis = mcVideoStream ? (mcCursorAusgeblendet ? ' · ohne Mauszeiger' : ' · mit Mauszeiger') : '';
|
||||
el('record-video-summary').textContent =
|
||||
RESOLUTION_LABEL[mcResolution] + groesse + ' · ' + QUALITAET_LABEL[mcQuality] + ' · ' + mcFramerate + ' fps' + cursorHinweis;
|
||||
RESOLUTION_LABEL[mcResolution] + groesse + ' · ' + QUALITAET_LABEL[mcQuality] + ' · ' + mcFramerate + ' fps';
|
||||
const aktiv = recordingActive();
|
||||
el('record-resolution').disabled = aktiv;
|
||||
el('record-quality').disabled = aktiv;
|
||||
@@ -3290,7 +3307,6 @@ async function onMcRecordingStopped() {
|
||||
clearInterval(mcTimerInterval);
|
||||
window.api.recordingSetGuard(false);
|
||||
el('record-live-badge').classList.add('hidden');
|
||||
el('record-toggle').textContent = 'Aufnahme starten';
|
||||
el('record-timer').textContent = '00:00';
|
||||
updateVideoSummary();
|
||||
cleanupAudioMix();
|
||||
@@ -3301,7 +3317,16 @@ async function onMcRecordingStopped() {
|
||||
const buffer = await blob.arrayBuffer();
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
mcLastSavedInstanceName = inst ? inst.name : 'Instanz';
|
||||
const res = await window.api.recordingSave(mcLastSavedInstanceName, buffer, await aktuellerAufnahmeServer());
|
||||
|
||||
// Die Umwandlung nach MP4 (für Abspielbarkeit in jedem Player) läuft nicht
|
||||
// sofort durch - kurz sichtbar machen, damit der Knopf währenddessen nicht
|
||||
// wie eingefroren wirkt
|
||||
const knopf = el('record-toggle');
|
||||
knopf.disabled = true;
|
||||
knopf.textContent = 'Wird verarbeitet…';
|
||||
const res = await window.api.recordingSave(mcLastSavedInstanceName, buffer, await aktuellerAufnahmeServer(), mcFramerate);
|
||||
knopf.disabled = false;
|
||||
knopf.textContent = 'Aufnahme starten';
|
||||
if (res.ok) {
|
||||
toast('Aufzeichnung gespeichert.');
|
||||
} else {
|
||||
@@ -3338,6 +3363,8 @@ async function openScreens() {
|
||||
grid.innerHTML = '<div class="screens-empty">Lade …</div>';
|
||||
screenSelected = new Set();
|
||||
updateScreensSelectionUi();
|
||||
recordsGeladen = false;
|
||||
switchScreensTab('photos');
|
||||
show('modal-screens');
|
||||
|
||||
const shots = await window.api.screenshots(currentDetailId);
|
||||
@@ -3402,6 +3429,251 @@ async function deleteSelectedScreenshots() {
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Aufzeichnungen-Galerie (im selben Dialog, zweiter Tab) ----------
|
||||
let screensAktiverTab = 'photos';
|
||||
let recordSelected = new Set();
|
||||
let recordsGeladen = false; // nur beim ersten Wechsel auf den Tab laden, nicht bei jedem Öffnen
|
||||
|
||||
function switchScreensTab(which) {
|
||||
screensAktiverTab = which;
|
||||
const photos = which === 'photos';
|
||||
el('screens-tab-photos').classList.toggle('on', photos);
|
||||
el('screens-tab-records').classList.toggle('on', !photos);
|
||||
el('screens-pane-photos').classList.toggle('hidden', !photos);
|
||||
el('screens-pane-records').classList.toggle('hidden', photos);
|
||||
if (!photos && !recordsGeladen) { recordsGeladen = true; openRecordsList(); }
|
||||
}
|
||||
|
||||
function updateRecordsSelectionUi() {
|
||||
const n = recordSelected.size;
|
||||
const btn = el('records-delete-sel');
|
||||
if (btn) {
|
||||
btn.disabled = n === 0;
|
||||
btn.textContent = n ? (n + ' löschen') : 'Auswahl löschen';
|
||||
}
|
||||
const hint = el('records-sel-hint');
|
||||
if (hint) hint.textContent = n ? (n + ' ausgewählt – Doppelklick spielt ab') : 'Klick auf Vorschaubild zum Auswählen, Doppelklick zum Abspielen';
|
||||
document.querySelectorAll('#records-grid .screen-thumb').forEach((t) => {
|
||||
const name = t.dataset.name;
|
||||
t.classList.toggle('selected', !!(name && recordSelected.has(name)));
|
||||
const cb = t.querySelector('.screen-check');
|
||||
if (cb) cb.checked = !!(name && recordSelected.has(name));
|
||||
});
|
||||
}
|
||||
|
||||
function formatDateigroesse(bytes) {
|
||||
if (bytes >= 1024 * 1024 * 1024) return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
|
||||
if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
return Math.round(bytes / 1024) + ' KB';
|
||||
}
|
||||
|
||||
async function openRecordsList() {
|
||||
const grid = el('records-grid');
|
||||
grid.innerHTML = '<div class="screens-empty">Lade …</div>';
|
||||
recordSelected = new Set();
|
||||
updateRecordsSelectionUi();
|
||||
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
const instanceName = inst ? inst.name : null;
|
||||
if (!instanceName) { grid.innerHTML = '<div class="screens-empty">Keine Instanz ausgewählt.</div>'; return; }
|
||||
|
||||
const aufnahmen = await window.api.recordingList(instanceName);
|
||||
grid.innerHTML = '';
|
||||
if (!aufnahmen.length) {
|
||||
grid.innerHTML = '<div class="screens-empty">Keine Aufzeichnungen vorhanden.</div>';
|
||||
updateRecordsSelectionUi();
|
||||
return;
|
||||
}
|
||||
for (const a of aufnahmen) {
|
||||
const thumb = document.createElement('div');
|
||||
thumb.className = 'screen-thumb record-thumb';
|
||||
thumb.dataset.name = a.name;
|
||||
thumb.innerHTML =
|
||||
`<input type="checkbox" class="screen-check" title="Auswählen" />
|
||||
<div class="record-thumb-img-wrap"><img alt="" /><span class="record-thumb-play">▶</span></div>
|
||||
<div class="screen-name" title="${escapeHtml(a.name)}">${escapeHtml(a.name)}</div>
|
||||
<div class="record-thumb-size">${formatDateigroesse(a.size)}</div>
|
||||
<button class="screen-del" title="Löschen">🗑</button>`;
|
||||
const img = thumb.querySelector('img');
|
||||
window.api.recordingThumbnail(instanceName, a.name).then((d) => { if (d) img.src = d; });
|
||||
|
||||
const toggle = () => {
|
||||
if (recordSelected.has(a.name)) recordSelected.delete(a.name);
|
||||
else recordSelected.add(a.name);
|
||||
updateRecordsSelectionUi();
|
||||
};
|
||||
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();
|
||||
});
|
||||
thumb.addEventListener('dblclick', (e) => {
|
||||
if (e.target.closest('.screen-del') || e.target.closest('.screen-check')) return;
|
||||
window.api.recordingOpen(instanceName, a.name);
|
||||
});
|
||||
thumb.querySelector('.screen-del').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
askConfirm('Aufzeichnung löschen', `„${a.name}" löschen?`, '🗑 Löschen', async () => {
|
||||
await window.api.recordingDelete(instanceName, a.name);
|
||||
openRecordsList();
|
||||
});
|
||||
});
|
||||
grid.appendChild(thumb);
|
||||
}
|
||||
updateRecordsSelectionUi();
|
||||
}
|
||||
|
||||
async function deleteSelectedRecords() {
|
||||
const names = [...recordSelected];
|
||||
if (!names.length || !currentDetailId) return;
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
const instanceName = inst ? inst.name : null;
|
||||
if (!instanceName) return;
|
||||
const label = names.length === 1
|
||||
? `„${names[0]}" löschen?`
|
||||
: `${names.length} Aufzeichnungen unwiderruflich löschen?`;
|
||||
askConfirm('Aufzeichnungen löschen', label, '🗑 Löschen', async () => {
|
||||
for (const name of names) {
|
||||
await window.api.recordingDelete(instanceName, name);
|
||||
}
|
||||
toast(names.length === 1 ? 'Aufzeichnung gelöscht.' : names.length + ' Aufzeichnungen gelöscht.');
|
||||
openRecordsList();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Freunde ----------
|
||||
let friendsAutoRefresh = null;
|
||||
|
||||
async function openFriends() {
|
||||
if (POPOUT !== 'friends') { window.api.popoutOpen('friends'); return; }
|
||||
show('modal-friends');
|
||||
await ladeFreunde();
|
||||
clearInterval(friendsAutoRefresh);
|
||||
friendsAutoRefresh = setInterval(ladeFreunde, 30000);
|
||||
}
|
||||
|
||||
async function ladeFreunde() {
|
||||
const aktiv = await window.api.friendsEnabled();
|
||||
el('friends-disabled-hint').classList.toggle('hidden', aktiv);
|
||||
el('friends-content').classList.toggle('hidden', !aktiv);
|
||||
if (!aktiv) return;
|
||||
|
||||
const [anfragenRes, gesendetRes, freundeRes, einstellungen] = await Promise.all([
|
||||
window.api.friendsRequests(),
|
||||
window.api.friendsSentRequests(),
|
||||
window.api.friendsList(),
|
||||
window.api.getSettings(),
|
||||
]);
|
||||
const spitznamen = einstellungen.friendNicknames || {};
|
||||
|
||||
const reqBox = el('friends-requests-box');
|
||||
reqBox.innerHTML = '';
|
||||
if (anfragenRes.ok && anfragenRes.list.length) {
|
||||
const titel = document.createElement('div');
|
||||
titel.className = 'friends-section-title';
|
||||
titel.textContent = 'Anfragen';
|
||||
reqBox.appendChild(titel);
|
||||
for (const r of anfragenRes.list) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'friend-row';
|
||||
row.innerHTML = `<img class="friend-avatar" src="https://crafthead.net/cube/${escapeHtml(r.uuid)}/64" alt="" />
|
||||
<div class="friend-info"><div class="friend-name">${escapeHtml(r.username || r.uuid)}</div></div>
|
||||
<button class="btn btn-mini" data-act="accept">Annehmen</button>
|
||||
<button class="btn btn-mini btn-danger" data-act="decline">Ablehnen</button>`;
|
||||
row.querySelector('.friend-avatar').addEventListener('error', function () { this.remove(); });
|
||||
row.querySelector('[data-act="accept"]').addEventListener('click', async () => {
|
||||
await window.api.friendsRespond(r.uuid, true);
|
||||
ladeFreunde();
|
||||
});
|
||||
row.querySelector('[data-act="decline"]').addEventListener('click', async () => {
|
||||
await window.api.friendsRespond(r.uuid, false);
|
||||
ladeFreunde();
|
||||
});
|
||||
reqBox.appendChild(row);
|
||||
}
|
||||
}
|
||||
if (gesendetRes.ok && gesendetRes.list.length) {
|
||||
const titel = document.createElement('div');
|
||||
titel.className = 'friends-section-title';
|
||||
titel.textContent = 'Gesendet';
|
||||
reqBox.appendChild(titel);
|
||||
for (const r of gesendetRes.list) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'friend-row';
|
||||
row.innerHTML = `<img class="friend-avatar" src="https://crafthead.net/cube/${escapeHtml(r.uuid)}/64" alt="" />
|
||||
<div class="friend-info"><div class="friend-name">${escapeHtml(r.username || r.uuid)}</div><div class="friend-status">wartet auf Bestätigung</div></div>
|
||||
<button class="btn btn-mini btn-danger" data-act="cancel">Zurückziehen</button>`;
|
||||
row.querySelector('.friend-avatar').addEventListener('error', function () { this.remove(); });
|
||||
row.querySelector('[data-act="cancel"]').addEventListener('click', async () => {
|
||||
await window.api.friendsCancel(r.uuid);
|
||||
ladeFreunde();
|
||||
});
|
||||
reqBox.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
const listBox = el('friends-list-box');
|
||||
listBox.innerHTML = '<div class="friends-section-title">Freunde</div>';
|
||||
if (!freundeRes.ok) {
|
||||
listBox.innerHTML += `<div class="friends-empty">${escapeHtml(freundeRes.message || 'Nicht erreichbar.')}</div>`;
|
||||
return;
|
||||
}
|
||||
if (!freundeRes.list.length) {
|
||||
listBox.innerHTML += '<div class="friends-empty">Noch keine Freunde. Namen oben eingeben, um eine Anfrage zu senden.</div>';
|
||||
return;
|
||||
}
|
||||
const sortiert = [...freundeRes.list].sort((a, b) => {
|
||||
if (a.online !== b.online) return a.online ? -1 : 1;
|
||||
return (a.username || '').localeCompare(b.username || '');
|
||||
});
|
||||
for (const f of sortiert) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'friend-row';
|
||||
const anzeigeName = spitznamen[f.uuid] || f.username || f.uuid;
|
||||
const status = f.online
|
||||
? (f.instance ? `spielt ${f.instance}${f.server ? ' · ' + f.server : ''}` : 'online')
|
||||
: 'offline';
|
||||
row.innerHTML = `<span class="friend-dot${f.online ? ' online' : ''}"></span>
|
||||
<img class="friend-avatar" src="https://crafthead.net/cube/${escapeHtml(f.uuid)}/64" alt="" />
|
||||
<div class="friend-info"><div class="friend-name">${escapeHtml(anzeigeName)}</div><div class="friend-status">${escapeHtml(status)}</div></div>
|
||||
<button class="btn btn-mini" data-act="nick" title="Spitzname">✎</button>
|
||||
<button class="btn btn-mini btn-danger" data-act="remove" title="Entfernen">🗑</button>`;
|
||||
row.querySelector('.friend-avatar').addEventListener('error', function () { this.remove(); });
|
||||
row.querySelector('[data-act="nick"]').addEventListener('click', async () => {
|
||||
const wert = await askPrompt({
|
||||
title: 'Spitzname',
|
||||
label: `Eigener Spitzname für ${f.username || f.uuid} (leer lassen zum Entfernen)`,
|
||||
value: spitznamen[f.uuid] || '',
|
||||
okLabel: 'Speichern',
|
||||
});
|
||||
if (wert === null) return; // Abgebrochen
|
||||
await window.api.friendsSetNickname(f.uuid, wert.trim());
|
||||
ladeFreunde();
|
||||
});
|
||||
row.querySelector('[data-act="remove"]').addEventListener('click', () => {
|
||||
askConfirm('Freund entfernen', `„${anzeigeName}" aus der Freundesliste entfernen?`, '🗑 Entfernen', async () => {
|
||||
await window.api.friendsRemove(f.uuid);
|
||||
ladeFreunde();
|
||||
});
|
||||
});
|
||||
listBox.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendeFreundschaftsanfrage() {
|
||||
const input = el('friends-add-input');
|
||||
const name = input.value.trim();
|
||||
if (!name) return;
|
||||
const res = await window.api.friendsAdd(name);
|
||||
if (res.ok) {
|
||||
toast('Anfrage gesendet.');
|
||||
input.value = '';
|
||||
ladeFreunde();
|
||||
} else {
|
||||
toast(res.message || 'Anfrage fehlgeschlagen.', true);
|
||||
}
|
||||
}
|
||||
|
||||
function renderContentList(boxId, items, sub, emoji) {
|
||||
const box = el(boxId);
|
||||
box.innerHTML = '';
|
||||
@@ -3846,6 +4118,7 @@ async function openSettings(pane) {
|
||||
el('s-iso3d').checked = s.iso3dIcons !== false;
|
||||
el('s-bedrock').checked = s.bedrockEnabled !== false;
|
||||
el('s-notify').checked = s.desktopNotifications !== false;
|
||||
el('s-friends').checked = s.friendsEnabled !== false;
|
||||
el('s-autostart').checked = !!s.autostart;
|
||||
el('s-auto-java').checked = s.autoJava !== false;
|
||||
el('s-autobackup').checked = !!s.autoBackupEnabled;
|
||||
@@ -3974,6 +4247,7 @@ async function saveSettings() {
|
||||
iso3dIcons: el('s-iso3d').checked,
|
||||
bedrockEnabled: el('s-bedrock').checked,
|
||||
desktopNotifications: el('s-notify').checked,
|
||||
friendsEnabled: el('s-friends').checked,
|
||||
autostart: el('s-autostart').checked,
|
||||
autoJava: el('s-auto-java').checked,
|
||||
autoBackupEnabled: el('s-autobackup').checked,
|
||||
@@ -4629,6 +4903,16 @@ function wire() {
|
||||
document.querySelectorAll('.btab').forEach((t) =>
|
||||
t.addEventListener('click', () => switchBedrockTab(t.dataset.kind)));
|
||||
|
||||
// Freunde
|
||||
el('btn-friends').addEventListener('click', openFriends);
|
||||
el('friends-add-btn').addEventListener('click', sendeFreundschaftsanfrage);
|
||||
el('friends-add-input').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') sendeFreundschaftsanfrage();
|
||||
});
|
||||
new MutationObserver(() => {
|
||||
if (el('modal-friends').classList.contains('hidden')) { clearInterval(friendsAutoRefresh); friendsAutoRefresh = null; }
|
||||
}).observe(el('modal-friends'), { attributes: true, attributeFilter: ['class'] });
|
||||
|
||||
// Konsole
|
||||
el('btn-console').addEventListener('click', openConsole);
|
||||
el('console-clear').addEventListener('click', () => { consoleBuffer = ''; el('console-out').textContent = ''; });
|
||||
@@ -4846,7 +5130,14 @@ function wire() {
|
||||
// Ordner-Buttons in den Inhalte-/Screenshots-Dialogen
|
||||
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-open').addEventListener('click', async () => {
|
||||
if (screensAktiverTab === 'records') {
|
||||
const inst = await window.api.getInstance(currentDetailId);
|
||||
if (inst) window.api.recordingOpenFolder(inst.name);
|
||||
} else {
|
||||
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);
|
||||
@@ -4858,6 +5149,19 @@ function wire() {
|
||||
updateScreensSelectionUi();
|
||||
});
|
||||
el('screens-delete-sel').addEventListener('click', () => deleteSelectedScreenshots());
|
||||
el('screens-tab-photos').addEventListener('click', () => switchScreensTab('photos'));
|
||||
el('screens-tab-records').addEventListener('click', () => switchScreensTab('records'));
|
||||
el('records-select-all').addEventListener('click', () => {
|
||||
document.querySelectorAll('#records-grid .screen-thumb').forEach((t) => {
|
||||
if (t.dataset.name) recordSelected.add(t.dataset.name);
|
||||
});
|
||||
updateRecordsSelectionUi();
|
||||
});
|
||||
el('records-select-none').addEventListener('click', () => {
|
||||
recordSelected.clear();
|
||||
updateRecordsSelectionUi();
|
||||
});
|
||||
el('records-delete-sel').addEventListener('click', () => deleteSelectedRecords());
|
||||
el('content-open-dp').addEventListener('click', () => window.api.openSubdir(currentDetailId, 'datapacks'));
|
||||
|
||||
// Mod-Dialog
|
||||
@@ -5172,6 +5476,7 @@ const POPOUT_OPENERS = {
|
||||
}
|
||||
},
|
||||
record: () => openRecording(),
|
||||
friends: () => openFriends(),
|
||||
};
|
||||
|
||||
function popoutBoot(params) {
|
||||
@@ -5209,7 +5514,22 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
applyTheme(s.theme);
|
||||
iso3dEnabled = s.iso3dIcons !== false;
|
||||
consoleMaxChars = Math.max(50000, (s.consoleMaxLines || 100000) * 3);
|
||||
el('btn-friends').classList.toggle('hidden', s.friendsEnabled === false);
|
||||
} catch { applyTheme('dark'); }
|
||||
// Zahl offener Freundschaftsanfragen am "Freunde"-Knopf (nur Hauptfenster
|
||||
// zeigt die Werkzeugleiste)
|
||||
if (!POPOUT) {
|
||||
const zeigeAnfragenZahl = (zahl) => {
|
||||
const badge = el('friends-badge');
|
||||
badge.textContent = zahl > 99 ? '99+' : String(zahl);
|
||||
badge.classList.toggle('hidden', !zahl);
|
||||
el('btn-friends').title = zahl
|
||||
? `Freunde – ${zahl} offene Anfrage${zahl === 1 ? '' : 'n'} (ingame: Strg+Alt+F zeigt, wer online ist)`
|
||||
: 'Freunde (ingame: Strg+Alt+F zeigt, wer online ist)';
|
||||
};
|
||||
window.api.onFriendsRequestCount(zeigeAnfragenZahl);
|
||||
window.api.friendsRequestCount().then(zeigeAnfragenZahl).catch(() => {});
|
||||
}
|
||||
await refreshSkinBackground();
|
||||
window.api.onLaunchProgress(onLaunchEvt);
|
||||
// Servername live in den Titel nachtragen, falls das Aufzeichnungs-Fenster
|
||||
@@ -5221,8 +5541,14 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
window.api.onAuthCode(onAuthCode);
|
||||
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(); });
|
||||
// Spiel gestartet/beendet ODER das aktive Konto in einem ANDEREN Fenster
|
||||
// gewechselt (separater Renderer-Prozess, siehe cross-window state) ->
|
||||
// Kontenlisten, Kopfzeile und Skin-Hintergrund hier ebenfalls nachziehen
|
||||
window.api.onAccountsChanged(async () => {
|
||||
await refreshAllAccountLists();
|
||||
await refreshAccountButton();
|
||||
await refreshSkinBackground();
|
||||
});
|
||||
// über eine Desktop-Verknüpfung gestartet -> Instanz auswählen und anwerfen
|
||||
// (bei einer Server-Verknüpfung zusätzlich direkt mit dem Server verbinden)
|
||||
window.api.onLaunchRequest(async (payload) => {
|
||||
@@ -5247,7 +5573,12 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
window.api.onSettingsChanged((s) => {
|
||||
applyTheme(s.theme);
|
||||
iso3dEnabled = s.iso3dIcons !== false;
|
||||
if (!POPOUT) { applyBedrockVisibility(); refreshSkinBackground(); reload(); }
|
||||
if (!POPOUT) {
|
||||
applyBedrockVisibility();
|
||||
refreshSkinBackground();
|
||||
reload();
|
||||
el('btn-friends').classList.toggle('hidden', s.friendsEnabled === false);
|
||||
}
|
||||
});
|
||||
// Ein Skin-Wechsel oder eine neue/aktualisierte Instanz aus einem anderen
|
||||
// Fenster (Skin-/Import-Popup) betrifft ebenfalls nur die Anzeige im
|
||||
|
||||
Reference in New Issue
Block a user