Initial QOffice implementation
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
ipcRenderer.on("qoffice:command", (_event, command) => {
|
||||
if (typeof command === "string") {
|
||||
window.dispatchEvent(new CustomEvent("qoffice-command", { detail: { command } }));
|
||||
}
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("qoffice", {
|
||||
isDesktop: true,
|
||||
platform: process.platform,
|
||||
openOfficeFile: (kind) => ipcRenderer.invoke("qoffice:open-office-file", kind),
|
||||
saveOfficeFile: (kind, defaultFileName, base64Data) =>
|
||||
ipcRenderer.invoke("qoffice:save-office-file", {
|
||||
kind,
|
||||
defaultFileName,
|
||||
base64Data
|
||||
}),
|
||||
exportPdf: (defaultFileName) => ipcRenderer.invoke("qoffice:export-pdf", defaultFileName)
|
||||
});
|
||||
Reference in New Issue
Block a user