Upload via GUI (85 Dateien)
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
// Startanimation des Hauptfensters (abschaltbar unter Einstellungen → Darstellung).
|
||||
//
|
||||
// Ablauf: Ein randloses, durchsichtiges Fenster liegt genau dort, wo das Programm erscheinen wird,
|
||||
// und zeigt das sich drehende Logo samt Schriftzug. Ist die Oberfläche geladen, wird das noch
|
||||
// unsichtbare Hauptfenster abfotografiert und dieses Bild im Startfenster von der Mitte nach außen
|
||||
// aufgezogen. Am Ende übernimmt das echte Fenster an exakt derselben Stelle, so wirkt es, als baue
|
||||
// sich das Programm auf.
|
||||
//
|
||||
// Das Startfenster wird VOR dem Hauptfenster angelegt (begin), damit das Logo so früh wie möglich
|
||||
// erscheint. Danach lädt das Hauptfenster, und play() übernimmt den Rest.
|
||||
|
||||
const { BrowserWindow } = require('electron');
|
||||
|
||||
const MIN_SPIN_MS = 1600; // so lange dreht sich das Logo mindestens
|
||||
const MAX_WAIT_MS = 12000; // spätestens dann wird das Hauptfenster gezeigt
|
||||
const MAX_GROW_MS = 2500; // der Aufbau darf nie länger dauern, sonst bliebe das Fenster unsichtbar
|
||||
const MAX_PAINT_WAIT_MS = 1500;
|
||||
const IGNORE_MOUSE_DELAY_MS = 600; // Mausereignisse erst nach dem ersten Zeichnen ignorieren, sonst wird das Fenster weiß
|
||||
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
// Wie lange noch gewartet werden muss, damit das Logo mindestens minMs lang zu sehen war.
|
||||
function remainingMs(shownAt, now, minMs) {
|
||||
if (!shownAt) return 0;
|
||||
return Math.max(0, minMs - (now - shownAt));
|
||||
}
|
||||
|
||||
// Position und Größe des Hauptfensters, zentriert im Arbeitsbereich. Wird für Start- und Hauptfenster
|
||||
// gleich berechnet, damit beide exakt übereinander liegen.
|
||||
function centeredBounds(workArea, width, height) {
|
||||
const w = Math.min(width, workArea.width);
|
||||
const h = Math.min(height, workArea.height);
|
||||
return {
|
||||
x: Math.round(workArea.x + (workArea.width - w) / 2),
|
||||
y: Math.round(workArea.y + (workArea.height - h) / 2),
|
||||
width: w,
|
||||
height: h
|
||||
};
|
||||
}
|
||||
|
||||
// Legt das Startfenster an und zeigt es, sobald es geladen ist.
|
||||
// Liefert { window, shownAt }: shownAt ist ein Promise auf den Zeitpunkt, an dem das Logo sichtbar wurde.
|
||||
function begin({ htmlPath, bounds }) {
|
||||
const window = new BrowserWindow({
|
||||
x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height,
|
||||
frame: false, transparent: true, resizable: false, movable: false,
|
||||
minimizable: false, maximizable: false, fullscreenable: false,
|
||||
skipTaskbar: true, hasShadow: false, focusable: false, show: false,
|
||||
backgroundColor: '#00000000', title: 'Git Manager',
|
||||
webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true, spellcheck: false }
|
||||
});
|
||||
const shownAt = window.loadFile(htmlPath).then(() => {
|
||||
if (window.isDestroyed()) return 0;
|
||||
window.showInactive();
|
||||
// Das Startfenster soll keine Mausklicks schlucken. WICHTIG: Der Aufruf muss NACH dem Zeigen und etwas
|
||||
// verzögert erfolgen. Davor aufgerufen wird das durchsichtige Fenster unter Windows weiß (getestet).
|
||||
setTimeout(() => { if (!window.isDestroyed()) window.setIgnoreMouseEvents(true); }, IGNORE_MOUSE_DELAY_MS);
|
||||
return Date.now();
|
||||
}).catch(() => 0);
|
||||
return { window, shownAt };
|
||||
}
|
||||
|
||||
// Wartet, bis die Oberfläche gezeichnet ist (Liste da oder nach höchstens MAX_PAINT_WAIT_MS),
|
||||
// sonst wüchse ein halbleeres Fenster auf.
|
||||
async function waitForPaintedUi(webContents) {
|
||||
const check = (expression) => webContents.executeJavaScript(expression, true).catch(() => true);
|
||||
const until = Date.now() + MAX_PAINT_WAIT_MS;
|
||||
while (Date.now() < until) {
|
||||
if (await check("document.readyState === 'complete' && (document.querySelectorAll('#explorerGrid > *').length > 0)")) break;
|
||||
await wait(100);
|
||||
}
|
||||
await wait(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt die Startanimation und zeigt am Ende das Hauptfenster.
|
||||
* @param {BrowserWindow} mainWin Hauptfenster, noch nicht angezeigt, an denselben Bounds wie das Startfenster
|
||||
* @param {Promise<void>} uiReady wird erfüllt, sobald die Oberfläche geladen ist
|
||||
* @param {{window: BrowserWindow, shownAt: Promise<number>}} startHandle Ergebnis von begin()
|
||||
*/
|
||||
async function play(mainWin, uiReady, startHandle) {
|
||||
const start = startHandle.window;
|
||||
|
||||
const showMain = () => {
|
||||
if (mainWin.isDestroyed()) return;
|
||||
mainWin.setOpacity(1);
|
||||
mainWin.setIgnoreMouseEvents(false);
|
||||
if (!mainWin.isVisible()) mainWin.show();
|
||||
mainWin.focus();
|
||||
};
|
||||
|
||||
try {
|
||||
const shownAt = await startHandle.shownAt;
|
||||
await Promise.race([uiReady, wait(MAX_WAIT_MS)]);
|
||||
await wait(remainingMs(shownAt, Date.now(), MIN_SPIN_MS));
|
||||
if (mainWin.isDestroyed()) return;
|
||||
|
||||
// Unsichtbar zeigen, damit Chromium die Oberfläche zeichnet, dann abfotografieren
|
||||
mainWin.setOpacity(0);
|
||||
mainWin.setIgnoreMouseEvents(true); // unsichtbar: Klicks gehen nicht versehentlich hinein
|
||||
mainWin.show();
|
||||
await waitForPaintedUi(mainWin.webContents);
|
||||
if (mainWin.isDestroyed()) return;
|
||||
const image = await mainWin.webContents.capturePage();
|
||||
if (!image.isEmpty() && !start.isDestroyed()) {
|
||||
await Promise.race([start.webContents.executeJavaScript(`aufbauen(${JSON.stringify(image.toDataURL())})`), wait(MAX_GROW_MS)]);
|
||||
}
|
||||
} catch (_) {
|
||||
// Die Animation ist nur Zierde: im Fehlerfall einfach das Hauptfenster zeigen
|
||||
} finally {
|
||||
showMain();
|
||||
setTimeout(() => { if (!start.isDestroyed()) start.destroy(); }, 80);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { begin, play, remainingMs, centeredBounds, MIN_SPIN_MS };
|
||||
Reference in New Issue
Block a user