Gregorfun's picture
Initial commit
32c5da4
const { app, BrowserWindow, dialog, ipcMain, shell, nativeImage } = require("electron");
const path = require("path");
const fs = require("fs");
const { spawn } = require("child_process");
const BACKEND_URL = "http://127.0.0.1:8008/health";
let backendProcess = null;
function projectRoot() {
return path.resolve(__dirname, "..", "..");
}
function isDev() {
return process.env.IMAGEFORGE_DEV === "1";
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForBackend(timeoutMs = 20000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
try {
const response = await fetch(BACKEND_URL);
if (response.ok) {
return true;
}
} catch (_) {}
await sleep(500);
}
return false;
}
function startBackend() {
const root = projectRoot();
const backendModule = "backend.app.main";
const pythonCandidates = process.platform === "win32"
? [
path.join(root, ".venv", "Scripts", "python.exe"),
path.join(root, "..", ".venv", "Scripts", "python.exe"),
"py",
"python",
]
: [
path.join(root, ".venv", "bin", "python"),
path.join(root, "..", ".venv", "bin", "python"),
"python3",
"python",
];
for (const command of pythonCandidates) {
if (command.includes(path.sep) && !fs.existsSync(command)) {
continue;
}
try {
const args = command === "py" ? ["-3", "-m", backendModule] : ["-m", backendModule];
const child = spawn(command, args, {
cwd: root,
env: {
...process.env,
IMAGEFORGE_HOST: process.env.IMAGEFORGE_HOST || "127.0.0.1",
IMAGEFORGE_PORT: process.env.IMAGEFORGE_PORT || "8008",
IMAGEFORGE_CORS_ORIGINS: process.env.IMAGEFORGE_CORS_ORIGINS || "http://localhost:5173",
IMAGEFORGE_CACHE_ROOT: process.env.IMAGEFORGE_CACHE_ROOT || path.join(root, ".cache"),
IMAGEFORGE_TMP_ROOT: process.env.IMAGEFORGE_TMP_ROOT || path.join(root, ".cache", "tmp"),
},
stdio: "ignore",
detached: false,
});
child.once("error", () => {});
backendProcess = child;
return;
} catch (_) {}
}
}
async function createWindow() {
startBackend();
const healthy = await waitForBackend();
if (!healthy) {
dialog.showErrorBox(
"Backend not reachable",
"ImageForge konnte das lokale Backend nicht starten. Bitte Python und requirements installieren."
);
}
const win = new BrowserWindow({
width: 1600,
height: 920,
minWidth: 1100,
minHeight: 700,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, "preload.js"),
nodeIntegration: false,
},
});
const devServerUrl = process.env.VITE_DEV_SERVER_URL;
if (devServerUrl) {
await win.loadURL(devServerUrl);
} else {
await win.loadFile(path.join(__dirname, "..", "dist", "index.html"));
}
}
ipcMain.handle("open-folder", async (_event, folderPath) => {
await shell.openPath(folderPath);
});
ipcMain.handle("save-image", async (_event, sourcePath, defaultName) => {
const result = await dialog.showSaveDialog({
title: "Save image",
defaultPath: defaultName,
filters: [
{ name: "PNG", extensions: ["png"] },
{ name: "JPEG", extensions: ["jpg", "jpeg"] },
],
});
if (result.canceled || !result.filePath) {
return false;
}
const ext = path.extname(result.filePath).toLowerCase();
if (ext === ".jpg" || ext === ".jpeg") {
const img = nativeImage.createFromPath(sourcePath);
fs.writeFileSync(result.filePath, img.toJPEG(92));
} else {
fs.copyFileSync(sourcePath, result.filePath);
}
return true;
});
ipcMain.handle("show-error", async (_event, title, message) => {
await dialog.showMessageBox({
type: "error",
title,
message,
});
});
ipcMain.handle("pick-image", async () => {
const result = await dialog.showOpenDialog({
title: "Select source image",
properties: ["openFile"],
filters: [{ name: "Images", extensions: ["png", "jpg", "jpeg", "webp", "bmp"] }],
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
return result.filePaths[0];
});
ipcMain.handle("read-image-data-url", async (_event, sourcePath) => {
try {
const root = projectRoot();
const outputRoot = path.resolve(root, "output");
const resolved = path.resolve(String(sourcePath || ""));
if (resolved !== outputRoot && !resolved.startsWith(`${outputRoot}${path.sep}`)) {
return null;
}
if (!fs.existsSync(resolved)) {
return null;
}
const ext = path.extname(resolved).toLowerCase();
const mimeMap = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".bmp": "image/bmp",
};
const mime = mimeMap[ext] || "application/octet-stream";
const data = fs.readFileSync(resolved);
return `data:${mime};base64,${data.toString("base64")}`;
} catch {
return null;
}
});
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (backendProcess && !backendProcess.killed) {
backendProcess.kill();
}
if (process.platform !== "darwin") {
app.quit();
}
});