diff --git a/src/aufbau.html b/src/aufbau.html new file mode 100644 index 0000000..9588e2e --- /dev/null +++ b/src/aufbau.html @@ -0,0 +1,51 @@ + + + + + + AeroMC + + + + + + + diff --git a/src/cfimport.js b/src/cfimport.js index 5da5001..918b048 100644 --- a/src/cfimport.js +++ b/src/cfimport.js @@ -154,14 +154,27 @@ function scan(inputPath) { return { ok: true, instancesDir, count: list.length, instances: list }; } +function kopierNamen(sourceDir) { + return fs.readdirSync(sourceDir).filter((name) => !META_FILES.includes(name)); +} + function copyInto(sourceDir, targetDir) { fs.mkdirSync(targetDir, { recursive: true }); - for (const name of fs.readdirSync(sourceDir)) { - if (META_FILES.includes(name)) continue; + for (const name of kopierNamen(sourceDir)) { fs.cpSync(path.join(sourceDir, name), path.join(targetDir, name), { recursive: true }); } } +/* + * Was beim Übernehmen kopiert wird: [{ von, name }] – `name` ist der Zielname + * im Spielordner der neuen Instanz. Das Kopieren selbst erledigt kopieren.js + * asynchron mit Fortschritt (main.js → fuehreImportAus). + */ +function kopierQuellen(entry) { + if (!entry.gameDir || !fs.existsSync(entry.gameDir)) return []; + return kopierNamen(entry.gameDir).map((name) => ({ von: path.join(entry.gameDir, name), name })); +} + function importOne(store, entry, copyData = true) { const created = store.createOrReplaceImportedInstance({ name: entry.name, @@ -193,4 +206,4 @@ function importOne(store, entry, copyData = true) { return { id: created.id, name: created.name, copied }; } -module.exports = { scan, importOne, resolveInstancesDir }; \ No newline at end of file +module.exports = { scan, importOne, kopierQuellen, resolveInstancesDir }; \ No newline at end of file diff --git a/src/designs.css b/src/designs.css index ed874e4..79032d6 100644 --- a/src/designs.css +++ b/src/designs.css @@ -308,6 +308,8 @@ :root[data-design="minecraft"] .list{image-rendering:auto} :root[data-design="minecraft"] .group-header{font-family:var(--pixel);font-size:16px;font-weight:400;color:#fff;text-shadow:var(--mc-schatten);padding:8px 4px 4px} :root[data-design="minecraft"] .group-count{color:#b8b9bc} +:root[data-design="minecraft"] .group-sub > .group-header{font-size:14px} +:root[data-design="minecraft"] .group-sub{border-left:2px solid rgba(0,0,0,.45)} :root[data-design="minecraft"] .group-header .chev{color:#fff;font-family:"Segoe UI",sans-serif;font-size:10px} :root[data-design="minecraft"] .group-header::after{height:2px;background:rgba(0,0,0,.55);box-shadow:0 1px 0 rgba(255,255,255,.08)} :root[data-design="minecraft"] .card{border-radius:0} diff --git a/src/fensteraufbau.js b/src/fensteraufbau.js new file mode 100644 index 0000000..2f9724d --- /dev/null +++ b/src/fensteraufbau.js @@ -0,0 +1,85 @@ +/* + * "Fenster wachsen aus dem Knopf": Ein Popup-Fenster (Optionen, Mods, Skin, + * Freunde …) erscheint nicht schlagartig, sondern wächst aus dem angeklickten + * Knopf heraus. Dafür wird das noch unsichtbare Fenster abfotografiert und das + * Bild in einem durchsichtigen Hilfsfenster vom Knopf aus groß gezogen; am Ende + * übernimmt das echte Fenster an exakt derselben Stelle. + * + * Das Hilfsfenster entsteht jedes Mal neu und gleich in der richtigen Größe: + * Unter Windows färbt sich ein durchsichtiges Fenster schwarz, sobald es + * nachträglich vergrößert wird, und ein verstecktes Fenster zeichnet nicht mehr. + * Abschaltbar unter Optionen → Darstellung → Animationen („Bewegte Oberfläche“). + */ +const path = require('path'); +const { BrowserWindow } = require('electron'); + +const DAUER_MS = 280; +const HOECHSTENS_MS = 2000; // spätestens dann ist das Fenster in jedem Fall zu sehen +const warte = (ms) => new Promise((r) => setTimeout(r, ms)); + +function vereinigung(a, b) { + const x = Math.min(a.x, b.x); + const y = Math.min(a.y, b.y); + return { + x, y, + width: Math.max(a.x + a.width, b.x + b.width) - x, + height: Math.max(a.y + a.height, b.y + b.height) - y, + }; +} + +// Rechteck in Koordinaten des Hilfsfensters umrechnen +const relativ = (r, box) => ({ x: r.x - box.x, y: r.y - box.y, width: r.width, height: r.height }); + +async function abspielen(ziel, von, merke) { + // unsichtbar zeigen, damit Chromium den Inhalt zeichnet, dann abfotografieren + ziel.setOpacity(0); + ziel.setIgnoreMouseEvents(true); + ziel.show(); + await warte(90); + if (ziel.isDestroyed()) return; + const bild = await ziel.webContents.capturePage(); + if (bild.isEmpty() || ziel.isDestroyed()) return; + const nach = ziel.getBounds(); + const box = vereinigung(von, nach); + + const hilfe = new BrowserWindow({ + x: box.x, y: box.y, width: box.width, height: box.height, + show: false, frame: false, transparent: true, resizable: false, movable: false, + minimizable: false, maximizable: false, fullscreenable: false, focusable: false, + skipTaskbar: true, hasShadow: false, alwaysOnTop: true, backgroundColor: '#00000000', + title: 'AeroMC', + webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true }, + }); + merke(hilfe); + hilfe.setIgnoreMouseEvents(true); + await hilfe.loadFile(path.join(__dirname, 'aufbau.html')); + const ok = await hilfe.webContents.executeJavaScript( + `vorbereiten(${JSON.stringify(bild.toDataURL())}, ${JSON.stringify(relativ(nach, box))}, ${JSON.stringify(relativ(von, box))})`); + if (!ok || hilfe.isDestroyed()) return; + hilfe.showInactive(); + await hilfe.webContents.executeJavaScript(`abspielen(${DAUER_MS})`); +} + +/** + * Zeigt `ziel` (geladen, aber noch nicht sichtbar) mit Wachs-Animation an. + * `von` ist das Rechteck des angeklickten Knopfs in Bildschirmkoordinaten. + */ +async function wachsen(ziel, von) { + let hilfe = null; + try { + await Promise.race([abspielen(ziel, von, (h) => { hilfe = h; }), warte(HOECHSTENS_MS)]); + } catch { + // Animation ist nur Zierde – im Fehlerfall das Fenster einfach zeigen + } finally { + if (!ziel.isDestroyed()) { + ziel.setOpacity(1); + ziel.setIgnoreMouseEvents(false); + if (!ziel.isVisible()) ziel.show(); + ziel.focus(); + } + // Hilfsfenster erst nach dem Umschalten schließen, sonst blitzt der Hintergrund durch + setTimeout(() => { if (hilfe && !hilfe.isDestroyed()) hilfe.destroy(); }, 60); + } +} + +module.exports = { wachsen }; diff --git a/src/index.html b/src/index.html index 233b100..28c5e0d 100644 --- a/src/index.html +++ b/src/index.html @@ -31,13 +31,14 @@ - +
+
+
+
Animationen
+ + + Startanimation: Das Logo dreht sich, danach baut sich das Fenster von der Mitte aus auf (ab dem nächsten Start, nicht im Infobereich). Bewegte Oberfläche: Die Skin-Figur atmet und schaut dem Mauszeiger nach, Fenster wachsen aus dem angeklickten Knopf, Instanzen blenden ein, Designs blenden weich über. +
+
Skin-Hintergrund
+ +

Importiert eine einzelne Instanz aus einem ZIP-Archiv.

Erkannt werden AeroMC-Exporte (instance.json) und einzelne MultiMC- oder Prism-Instanzen (instance.cfg) – letztere auch als gezippter Instanzordner. Der Launcher legt daraus automatisch eine neue Instanz an, samt Mods, Welten und Konfiguration.

@@ -1828,6 +1851,9 @@
+ + +