Files
QOffice/electron/main.cjs
T
Курнат Андрей 9bf4dd3672 Initial QOffice implementation
2026-06-01 05:31:05 +03:00

259 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { app, BrowserWindow, dialog, ipcMain, Menu, shell } = require("electron");
const fs = require("node:fs/promises");
const path = require("node:path");
const isDev = !app.isPackaged;
const fileKinds = {
docx: {
title: "Открыть документ Word",
saveTitle: "Сохранить документ Word",
defaultName: "Документ QWord.docx",
filters: [{ name: "Документы Word (.docx)", extensions: ["docx"] }]
},
xlsx: {
title: "Открыть книгу Excel",
saveTitle: "Сохранить книгу Excel",
defaultName: "Книга QExcell.xlsx",
filters: [{ name: "Книги Excel (.xlsx)", extensions: ["xlsx"] }]
},
pptx: {
title: "Открыть презентацию PowerPoint",
saveTitle: "Сохранить презентацию PowerPoint",
defaultName: "Презентация QPowerPoint.pptx",
filters: [{ name: "Презентации PowerPoint (.pptx)", extensions: ["pptx"] }]
},
eml: {
title: "Открыть письмо EML",
saveTitle: "Сохранить письмо EML",
defaultName: "Письмо.eml",
filters: [{ name: "Письма EML (.eml)", extensions: ["eml"] }]
},
mail: {
title: "Открыть письмо Outlook",
saveTitle: "Сохранить письмо EML",
defaultName: "Письмо.eml",
filters: [{ name: "Письма Outlook (.eml, .msg)", extensions: ["eml", "msg"] }]
}
};
function validatedKind(kind) {
if (typeof kind !== "string" || !Object.hasOwn(fileKinds, kind)) {
throw new Error("Неподдерживаемый тип файла QOffice.");
}
return fileKinds[kind];
}
function safeDefaultName(kind, defaultFileName) {
const config = validatedKind(kind);
const extension = config.filters[0].extensions[0];
return safeFileNameWithExtension(defaultFileName, extension, config.defaultName);
}
function safeFileNameWithExtension(defaultFileName, extension, fallbackName) {
const candidate = typeof defaultFileName === "string" && defaultFileName.trim() ? path.basename(defaultFileName.trim()) : fallbackName;
return candidate.toLowerCase().endsWith(`.${extension}`) ? candidate : `${candidate}.${extension}`;
}
function ownerWindow(event) {
return BrowserWindow.fromWebContents(event.sender) ?? BrowserWindow.getFocusedWindow() ?? undefined;
}
function sendFocusedCommand(command) {
const focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow && !focusedWindow.isDestroyed()) {
focusedWindow.webContents.send("qoffice:command", command);
}
}
function createApplicationMenu() {
const isMac = process.platform === "darwin";
const template = [
...(isMac
? [
{
label: "QOffice",
submenu: [
{ role: "about", label: "О QOffice" },
{ type: "separator" },
{ role: "hide", label: "Скрыть QOffice" },
{ role: "hideOthers", label: "Скрыть остальные" },
{ role: "unhide", label: "Показать все" },
{ type: "separator" },
{ role: "quit", label: "Выйти из QOffice" }
]
}
]
: []),
{
label: "Файл",
submenu: [
{ label: "Открыть...", accelerator: "CmdOrCtrl+O", click: () => sendFocusedCommand("open") },
{ label: "Сохранить как...", accelerator: "CmdOrCtrl+S", click: () => sendFocusedCommand("save") },
{ type: "separator" },
{ label: "Экспорт в PDF...", accelerator: "CmdOrCtrl+Shift+E", click: () => sendFocusedCommand("export-pdf") },
{ type: "separator" },
{ label: "Печать...", accelerator: "CmdOrCtrl+P", click: () => sendFocusedCommand("print") },
{ type: "separator" },
isMac ? { role: "close", label: "Закрыть окно" } : { role: "quit", label: "Выход" }
]
},
{
label: "Правка",
submenu: [
{ role: "undo", label: "Отменить" },
{ role: "redo", label: "Повторить" },
{ type: "separator" },
{ role: "cut", label: "Вырезать" },
{ role: "copy", label: "Копировать" },
{ role: "paste", label: "Вставить" },
{ role: "selectAll", label: "Выделить все" }
]
},
{
label: "Вид",
submenu: [
{ role: "resetZoom", label: "Сбросить масштаб" },
{ role: "zoomIn", label: "Увеличить" },
{ role: "zoomOut", label: "Уменьшить" },
{ type: "separator" },
{ role: "togglefullscreen", label: "Во весь экран" }
]
},
{
label: "Окно",
submenu: [
{ role: "minimize", label: "Свернуть" },
{ role: "reload", label: "Перезагрузить" },
...(isDev ? [{ role: "toggleDevTools", label: "Инструменты разработчика" }] : [])
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
ipcMain.handle("qoffice:open-office-file", async (event, kind) => {
const config = validatedKind(kind);
const result = await dialog.showOpenDialog(ownerWindow(event), {
title: config.title,
properties: ["openFile"],
filters: config.filters
});
if (result.canceled || result.filePaths.length === 0) {
return { canceled: true };
}
const filePath = result.filePaths[0];
const bytes = await fs.readFile(filePath);
return {
canceled: false,
fileName: path.basename(filePath),
filePath,
base64Data: bytes.toString("base64")
};
});
ipcMain.handle("qoffice:save-office-file", async (event, payload) => {
if (!payload || typeof payload !== "object") {
throw new Error("Некорректный запрос сохранения файла QOffice.");
}
const { kind, defaultFileName, base64Data } = payload;
const config = validatedKind(kind);
if (typeof base64Data !== "string") {
throw new Error("Некорректные данные файла QOffice.");
}
const result = await dialog.showSaveDialog(ownerWindow(event), {
title: config.saveTitle,
defaultPath: safeDefaultName(kind, defaultFileName),
filters: config.filters
});
if (result.canceled || !result.filePath) {
return { canceled: true };
}
await fs.writeFile(result.filePath, Buffer.from(base64Data, "base64"));
return {
canceled: false,
fileName: path.basename(result.filePath),
filePath: result.filePath
};
});
ipcMain.handle("qoffice:export-pdf", async (event, defaultFileName) => {
const sourceWindow = ownerWindow(event);
if (!sourceWindow) {
throw new Error("Не удалось найти окно QOffice для экспорта PDF.");
}
const result = await dialog.showSaveDialog(sourceWindow, {
title: "Экспорт PDF",
defaultPath: safeFileNameWithExtension(defaultFileName, "pdf", "QOffice.pdf"),
filters: [{ name: "PDF (.pdf)", extensions: ["pdf"] }]
});
if (result.canceled || !result.filePath) {
return { canceled: true };
}
const pdf = await event.sender.printToPDF({
printBackground: true,
preferCSSPageSize: true
});
await fs.writeFile(result.filePath, pdf);
return {
canceled: false,
fileName: path.basename(result.filePath),
filePath: result.filePath
};
});
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1440,
height: 920,
minWidth: 1080,
minHeight: 720,
title: "QOffice",
backgroundColor: "#f6f8fb",
webPreferences: {
preload: path.join(__dirname, "preload.cjs"),
contextIsolation: true,
nodeIntegration: false
}
});
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: "deny" };
});
if (isDev) {
mainWindow.loadURL("http://127.0.0.1:5173");
} else {
mainWindow.loadFile(path.join(__dirname, "../dist/index.html"));
}
}
app.whenReady().then(() => {
createApplicationMenu();
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});