668 lines
24 KiB
JavaScript
668 lines
24 KiB
JavaScript
const { app, BrowserWindow, protocol, ipcMain, globalShortcut } = require('electron');
|
||
const path = require('path');
|
||
require('dotenv').config({path: path.join(__dirname, 'config.env')})
|
||
const isDev = require('electron-is-dev');
|
||
const fs = require('fs');
|
||
const axios = require('axios');
|
||
const { logger } = require('../../backend/src/config/logger');
|
||
const config = require('../src/config.json')
|
||
const { print } = require('pdf-to-printer');
|
||
const { generatePayslipPDF } = require('./pdfGenerator');
|
||
|
||
protocol.registerSchemesAsPrivileged([
|
||
{
|
||
scheme: 'app',
|
||
privileges: {
|
||
secure: true,
|
||
standard: true,
|
||
supportFetchAPI: true,
|
||
corsEnabled: true
|
||
}
|
||
}
|
||
]);
|
||
|
||
if (!Promise.withResolvers) {
|
||
Promise.withResolvers = function () {
|
||
let resolve, reject;
|
||
const promise = new Promise((res, rej) => {
|
||
resolve = res;
|
||
reject = rej;
|
||
});
|
||
return { promise, resolve, reject };
|
||
};
|
||
}
|
||
|
||
let mainWindow;
|
||
let appConfig = {};
|
||
const configPath = isDev
|
||
? path.join(__dirname, '../src/config.json')
|
||
: path.join(process.resourcesPath, 'config.json');
|
||
|
||
try {
|
||
logger.info(`[Config] Trying to load config from: ${configPath}`);
|
||
if (fs.existsSync(configPath)) {
|
||
appConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||
logger.info('[Config] External config loaded successfully:', appConfig);
|
||
} else {
|
||
logger.warn('[Config] External config file not found. Using default values.');
|
||
}
|
||
} catch (error) {
|
||
logger.error('[Config] Could not load external config file:', error);
|
||
}
|
||
|
||
global.allowClose = false;
|
||
|
||
// ============================================
|
||
// 📄 IPC ОБРАБОТЧИКИ
|
||
// ============================================
|
||
|
||
// ✅ 1. Печать PDF (принимает URL или base64) - СКРЫТАЯ ПЕЧАТЬ
|
||
ipcMain.handle('print-pdf', async (event, data = {}) => {
|
||
let tempFile = null;
|
||
|
||
try {
|
||
if (!data.pdfUrl && !data.pdfBase64) {
|
||
throw new Error('URL или base64 PDF не передан');
|
||
}
|
||
|
||
let pdfBuffer;
|
||
|
||
// ✅ Если передан base64 — используем его
|
||
if (data.pdfBase64) {
|
||
logger.info('[PRINT] Печать PDF из base64');
|
||
pdfBuffer = Buffer.from(data.pdfBase64, 'base64');
|
||
logger.info('[PRINT] Размер PDF из base64:', pdfBuffer.length, 'байт');
|
||
}
|
||
// ✅ Если передан URL — скачиваем
|
||
else if (data.pdfUrl) {
|
||
const cleanUrl = data.pdfUrl.split('#')[0];
|
||
logger.info('[PRINT] Загрузка PDF из URL:', cleanUrl);
|
||
|
||
// Если это blob URL — получаем через fetch
|
||
if (cleanUrl.startsWith('blob:')) {
|
||
const response = await fetch(cleanUrl);
|
||
const blob = await response.blob();
|
||
pdfBuffer = Buffer.from(await blob.arrayBuffer());
|
||
} else {
|
||
const response = await axios.get(cleanUrl, {
|
||
responseType: 'arraybuffer',
|
||
timeout: 30000
|
||
});
|
||
pdfBuffer = Buffer.from(response.data);
|
||
}
|
||
logger.info('[PRINT] Размер PDF из URL:', pdfBuffer.length, 'байт');
|
||
}
|
||
|
||
if (!pdfBuffer || pdfBuffer.length < 100) {
|
||
throw new Error('PDF слишком маленький или пустой');
|
||
}
|
||
|
||
// ✅ ВРЕМЕННО СОХРАНЯЕМ ДЛЯ ПЕЧАТИ (pdf-to-printer требует файл)
|
||
const tempDir = app.getPath('temp');
|
||
tempFile = path.join(tempDir, `print_${Date.now()}.pdf`);
|
||
fs.writeFileSync(tempFile, pdfBuffer);
|
||
logger.info('[PRINT] Временный файл создан:', tempFile);
|
||
|
||
const printerName = data.deviceName || 'GP-80250 Series';
|
||
logger.info('[PRINT] СКРЫТАЯ печать через pdf-to-printer:', printerName);
|
||
|
||
// ✅ КЛЮЧЕВОЕ: silent: true — печать без диалога
|
||
await print(tempFile, {
|
||
printer: printerName,
|
||
silent: true,
|
||
printDialog: false
|
||
});
|
||
|
||
logger.info('[PRINT] ✅ Печать успешна');
|
||
|
||
// ✅ УДАЛЯЕМ ВРЕМЕННЫЙ ФАЙЛ
|
||
// try {
|
||
// if (tempFile && fs.existsSync(tempFile)) {
|
||
// fs.unlinkSync(tempFile);
|
||
// logger.info('[PRINT] Временный файл удален');
|
||
// }
|
||
// } catch (e) {
|
||
// logger.warn('[PRINT] Не удалось удалить временный файл:', e.message);
|
||
// }
|
||
|
||
return { success: true };
|
||
|
||
} catch (error) {
|
||
logger.error('[PRINT] ❌ Ошибка:', error.message);
|
||
try {
|
||
if (tempFile && fs.existsSync(tempFile)) {
|
||
fs.unlinkSync(tempFile);
|
||
}
|
||
} catch (e) {}
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|
||
|
||
// ✅ 2. ОСНОВНАЯ ЛОГИКА ПЕЧАТИ HTML (оставлена для совместимости)
|
||
async function printHtmlDirectLogic(data) {
|
||
let tempHtml = null;
|
||
let tempPdf = null;
|
||
let printWindow = null;
|
||
let savedPdfPath = null;
|
||
|
||
try {
|
||
if (!data.html) {
|
||
throw new Error('HTML-код не передан');
|
||
}
|
||
|
||
logger.info('[PRINT-HTML] ========== СОХРАНЕНИЕ PDF ==========');
|
||
logger.info('[PRINT-HTML] Длина HTML:', data.html.length);
|
||
|
||
const desktopPath = app.getPath('desktop');
|
||
const documentsPath = app.getPath('documents');
|
||
|
||
let saveDir = documentsPath;
|
||
const possiblePaths = [
|
||
path.join(documentsPath, 'payslips'),
|
||
path.join(documentsPath, 'Расчетные листки'),
|
||
path.join(desktopPath, 'payslips'),
|
||
path.join(desktopPath, 'Расчетные листки'),
|
||
documentsPath
|
||
];
|
||
|
||
for (const dir of possiblePaths) {
|
||
if (fs.existsSync(dir)) {
|
||
saveDir = dir;
|
||
logger.info('[PRINT-HTML] Найдена папка для сохранения:', saveDir);
|
||
break;
|
||
}
|
||
}
|
||
|
||
const now = new Date();
|
||
const dateStr = now.getFullYear() +
|
||
String(now.getMonth() + 1).padStart(2, '0') +
|
||
String(now.getDate()).padStart(2, '0') + '_' +
|
||
String(now.getHours()).padStart(2, '0') +
|
||
String(now.getMinutes()).padStart(2, '0') +
|
||
String(now.getSeconds()).padStart(2, '0');
|
||
|
||
const fileName = `test_${dateStr}.pdf`;
|
||
savedPdfPath = path.join(saveDir, fileName);
|
||
|
||
logger.info('[PRINT-HTML] PDF будет сохранен в:', savedPdfPath);
|
||
|
||
const tempDir = app.getPath('temp');
|
||
tempHtml = path.join(tempDir, `print_${Date.now()}.html`);
|
||
tempPdf = path.join(tempDir, `print_${Date.now()}.pdf`);
|
||
|
||
let htmlContent = data.html;
|
||
if (!htmlContent || htmlContent.trim().length < 10) {
|
||
logger.warn('[PRINT-HTML] HTML пустой, используем тестовый');
|
||
htmlContent = `
|
||
<div style="text-align:center;padding:20px;font-size:16pt;border:2px solid #000;">
|
||
<b>ТЕСТ ПЕЧАТИ</b>
|
||
<hr>
|
||
<div>Дата: ${new Date().toLocaleString()}</div>
|
||
<div>Табельный номер: TEST</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
const fullHtml = `<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Расчетный листок</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'Courier New', monospace;
|
||
padding: 20px;
|
||
background: white;
|
||
color: black;
|
||
font-size: 12pt;
|
||
}
|
||
.header {
|
||
text-align: center;
|
||
font-weight: bold;
|
||
font-size: 16pt;
|
||
border-bottom: 2px solid #000;
|
||
padding-bottom: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 4px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.total {
|
||
font-weight: bold;
|
||
border-top: 2px solid #000;
|
||
padding-top: 10px;
|
||
margin-top: 10px;
|
||
font-size: 14pt;
|
||
}
|
||
.section-title {
|
||
font-weight: bold;
|
||
border-bottom: 1px dotted #000;
|
||
margin: 10px 0;
|
||
padding: 5px 0;
|
||
font-size: 14pt;
|
||
}
|
||
.item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 3px 0 3px 15px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.footer {
|
||
text-align: center;
|
||
font-size: 10pt;
|
||
color: #666;
|
||
border-top: 1px dashed #000;
|
||
margin-top: 20px;
|
||
padding-top: 10px;
|
||
}
|
||
.debug-info {
|
||
color: #999;
|
||
font-size: 8pt;
|
||
margin-top: 10px;
|
||
padding: 5px;
|
||
border: 1px dashed #ccc;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${htmlContent}
|
||
<div class="debug-info">
|
||
Сгенерировано: ${new Date().toLocaleString()}
|
||
</div>
|
||
</body>
|
||
</html>`;
|
||
|
||
fs.writeFileSync(tempHtml, fullHtml, 'utf8');
|
||
logger.info('[PRINT-HTML] HTML файл создан:', tempHtml);
|
||
logger.info('[PRINT-HTML] Размер HTML:', fs.statSync(tempHtml).size, 'байт');
|
||
|
||
printWindow = new BrowserWindow({
|
||
width: 600,
|
||
height: 800,
|
||
show: true,
|
||
webPreferences: {
|
||
nodeIntegration: false,
|
||
contextIsolation: true,
|
||
webSecurity: false,
|
||
allowRunningInsecureContent: true,
|
||
}
|
||
});
|
||
|
||
printWindow.webContents.openDevTools();
|
||
|
||
logger.info('[PRINT-HTML] Загрузка HTML в окно...');
|
||
await printWindow.loadFile(tempHtml);
|
||
|
||
await new Promise((resolve) => {
|
||
printWindow.webContents.once('did-finish-load', () => {
|
||
logger.info('[PRINT-HTML] Событие did-finish-load');
|
||
resolve();
|
||
});
|
||
setTimeout(() => {
|
||
logger.info('[PRINT-HTML] Таймаут загрузки');
|
||
resolve();
|
||
}, 10000);
|
||
});
|
||
|
||
const contentCheck = await printWindow.webContents.executeJavaScript(`
|
||
(function() {
|
||
const body = document.body;
|
||
if (!body) return { hasBody: false, contentLength: 0 };
|
||
const text = body.textContent || '';
|
||
const html = body.innerHTML || '';
|
||
return {
|
||
hasBody: true,
|
||
contentLength: text.length,
|
||
htmlLength: html.length,
|
||
preview: text.substring(0, 200)
|
||
};
|
||
})()
|
||
`);
|
||
|
||
logger.info('[PRINT-HTML] Проверка контента:', contentCheck);
|
||
|
||
if (contentCheck.contentLength < 10) {
|
||
logger.warn('[PRINT-HTML] ⚠️ В окне нет контента!');
|
||
await printWindow.webContents.executeJavaScript(`
|
||
if (!document.body || document.body.textContent.length < 10) {
|
||
document.body.innerHTML = '<div style="padding:20px;font-size:20pt;color:red;">ОШИБКА: Контент не загрузился!</div>';
|
||
}
|
||
`);
|
||
}
|
||
|
||
await printWindow.webContents.executeJavaScript(`
|
||
document.body.style.display = 'block';
|
||
document.body.style.visibility = 'visible';
|
||
document.body.style.opacity = '1';
|
||
window.scrollTo(0, 0);
|
||
document.querySelectorAll('*').forEach(el => {
|
||
el.style.display = 'block';
|
||
el.style.visibility = 'visible';
|
||
el.style.opacity = '1';
|
||
});
|
||
console.log('Контент в окне:', document.body.textContent.length, 'символов');
|
||
console.log('HTML:', document.body.innerHTML.substring(0, 500));
|
||
`);
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
||
const finalCheck = await printWindow.webContents.executeJavaScript(`
|
||
document.body ? document.body.textContent.length : 0
|
||
`);
|
||
logger.info('[PRINT-HTML] Финальная проверка контента:', finalCheck, 'символов');
|
||
|
||
logger.info('[PRINT-HTML] Конвертация в PDF...');
|
||
const pdfData = await printWindow.webContents.printToPDF({
|
||
pageSize: {
|
||
width: 226800,
|
||
height: 500000
|
||
},
|
||
margins: {
|
||
top: 0,
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0
|
||
},
|
||
printBackground: true,
|
||
landscape: false
|
||
});
|
||
|
||
fs.writeFileSync(tempPdf, pdfData);
|
||
logger.info('[PRINT-HTML] PDF создан, размер:', pdfData.length, 'байт');
|
||
|
||
const pdfBuffer = fs.readFileSync(tempPdf);
|
||
const isPdf = pdfBuffer.slice(0, 4).toString() === '%PDF';
|
||
logger.info('[PRINT-HTML] Это PDF?', isPdf);
|
||
|
||
if (!isPdf) {
|
||
logger.error('[PRINT-HTML] ⚠️ Файл НЕ является PDF!');
|
||
throw new Error('Созданный файл не является PDF');
|
||
}
|
||
|
||
if (printWindow) {
|
||
printWindow.close();
|
||
printWindow = null;
|
||
}
|
||
|
||
if (pdfData.length < 500) {
|
||
logger.warn('[PRINT-HTML] ⚠️ PDF слишком маленький:', pdfData.length, 'байт');
|
||
logger.info('[PRINT-HTML] Пробуем еще раз с большим ожиданием...');
|
||
|
||
const retryWindow = new BrowserWindow({
|
||
width: 600,
|
||
height: 800,
|
||
show: false,
|
||
webPreferences: {
|
||
nodeIntegration: false,
|
||
contextIsolation: true,
|
||
webSecurity: false,
|
||
allowRunningInsecureContent: true,
|
||
}
|
||
});
|
||
|
||
await retryWindow.loadFile(tempHtml);
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
const retryPdf = await retryWindow.webContents.printToPDF({
|
||
pageSize: { width: 226800, height: 500000 },
|
||
margins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||
printBackground: true,
|
||
landscape: false
|
||
});
|
||
|
||
fs.writeFileSync(tempPdf, retryPdf);
|
||
logger.info('[PRINT-HTML] Повторный PDF создан, размер:', retryPdf.length, 'байт');
|
||
retryWindow.close();
|
||
}
|
||
|
||
fs.copyFileSync(tempPdf, savedPdfPath);
|
||
logger.info('[PRINT-HTML] ✅ PDF сохранен в:', savedPdfPath);
|
||
logger.info('[PRINT-HTML] Размер сохраненного PDF:', fs.statSync(savedPdfPath).size, 'байт');
|
||
|
||
const printerName = data.printerName || 'GP-80250 Series';
|
||
logger.info('[PRINT-HTML] Отправка PDF на печать, принтер:', printerName);
|
||
|
||
let printResult = null;
|
||
let printSuccess = false;
|
||
|
||
try {
|
||
logger.info('[PRINT-HTML] Попытка печати...');
|
||
printResult = await print(tempPdf, {
|
||
printer: printerName,
|
||
scale: 'fit',
|
||
paperSize: 'A4'
|
||
});
|
||
printSuccess = true;
|
||
logger.info('[PRINT-HTML] Печать успешна:', printResult);
|
||
} catch (err) {
|
||
logger.warn('[PRINT-HTML] Печать не удалась:', err.message);
|
||
try {
|
||
printResult = await print(tempPdf);
|
||
printSuccess = true;
|
||
logger.info('[PRINT-HTML] Печать успешна (без параметров)');
|
||
} catch (err2) {
|
||
logger.error('[PRINT-HTML] Все попытки печати не удались:', err2.message);
|
||
}
|
||
}
|
||
|
||
try {
|
||
if (tempHtml && fs.existsSync(tempHtml)) fs.unlinkSync(tempHtml);
|
||
if (tempPdf && fs.existsSync(tempPdf)) fs.unlinkSync(tempPdf);
|
||
} catch (e) {}
|
||
|
||
logger.info('[PRINT-HTML] ========== ЗАВЕРШЕНО ==========');
|
||
logger.info('[PRINT-HTML] ✅ PDF сохранен в:', savedPdfPath);
|
||
|
||
return {
|
||
success: true,
|
||
pdfPath: savedPdfPath,
|
||
printSuccess: printSuccess,
|
||
message: `PDF сохранен в: ${savedPdfPath}`
|
||
};
|
||
|
||
} catch (error) {
|
||
logger.error('[PRINT-HTML] ❌ Ошибка:', error.message);
|
||
logger.error('[PRINT-HTML] Стек:', error.stack);
|
||
|
||
try {
|
||
if (printWindow) printWindow.close();
|
||
if (tempHtml && fs.existsSync(tempHtml)) fs.unlinkSync(tempHtml);
|
||
if (tempPdf && fs.existsSync(tempPdf)) fs.unlinkSync(tempPdf);
|
||
} catch (e) {}
|
||
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// ✅ 3. Обработчик print-html-direct (для совместимости)
|
||
ipcMain.handle('print-html-direct', async (event, data) => {
|
||
return await printHtmlDirectLogic(data);
|
||
});
|
||
|
||
// ✅ 4. ОСНОВНОЙ ОБРАБОТЧИК - ГЕНЕРАЦИЯ PDF В ПАМЯТИ (БЕЗ СОХРАНЕНИЯ НА ДИСК)
|
||
ipcMain.handle('print-payslip-form', async (event, data = {}) => {
|
||
try {
|
||
logger.info('[PRINT-PAYSLIP] ========== ГЕНЕРАЦИЯ PDF В ПАМЯТИ ==========');
|
||
logger.info('[PRINT-PAYSLIP] tnom:', data.tnom);
|
||
logger.info('[PRINT-PAYSLIP] period:', data.period);
|
||
|
||
// --- 1. Получаем данные из бэкенда ---
|
||
let payslipData = null;
|
||
|
||
try {
|
||
const backendUrl = appConfig.backendUrl || 'http://localhost:3001';
|
||
const url = `${backendUrl}/api/payslip/data/${data.tnom}?period=${data.period}`;
|
||
logger.info('[PRINT-PAYSLIP] Запрос к бэкенду:', url);
|
||
|
||
const response = await axios.get(url, { timeout: 10000 });
|
||
payslipData = response.data;
|
||
logger.info('[PRINT-PAYSLIP] Данные получены');
|
||
} catch (error) {
|
||
logger.warn('[PRINT-PAYSLIP] Не удалось получить данные из бэкенда:', error.message);
|
||
// Заглушка на случай, если бэкенд не отвечает
|
||
payslipData = {
|
||
fullName: 'Иванов Иван Иванович',
|
||
tabNumber: data.tnom || '00001',
|
||
department: 'Отдел разработки',
|
||
position: 'Инженер-программист',
|
||
daysWorked: '22',
|
||
month: 'Ноябрь',
|
||
year: '2025',
|
||
period: data.period || '112025',
|
||
accrued: [
|
||
{ code: '01', description: 'Оклад', amount: 2891.87 },
|
||
{ code: '02', description: 'Премия', amount: 0 }
|
||
],
|
||
totalAccrued: 2891.87,
|
||
deductions: [
|
||
{ code: '101', description: 'НДФЛ', amount: 376.36 },
|
||
{ code: '102', description: 'Профсоюз', amount: 85.50 }
|
||
],
|
||
totalDeductions: 461.86,
|
||
totalPaid: 2430.01,
|
||
bankTransfers: [
|
||
{ bank: 'Сбербанк', amount: 2430.01 }
|
||
]
|
||
};
|
||
}
|
||
|
||
// --- 2. Генерируем PDF в памяти (Buffer) ---
|
||
const pdfBuffer = await generatePayslipPDF(payslipData);
|
||
logger.info('[PRINT-PAYSLIP] PDF сгенерирован, размер:', pdfBuffer.length, 'байт');
|
||
|
||
if (pdfBuffer.length < 100) {
|
||
throw new Error('Сгенерированный PDF слишком маленький');
|
||
}
|
||
|
||
// --- 3. Преобразуем Buffer в base64 для передачи через IPC ---
|
||
const pdfBase64 = pdfBuffer.toString('base64');
|
||
|
||
logger.info('[PRINT-PAYSLIP] PDF сконвертирован в base64, длина:', pdfBase64.length);
|
||
|
||
// --- 4. Возвращаем base64 PDF ---
|
||
return {
|
||
success: true,
|
||
pdfBase64: pdfBase64,
|
||
fileName: `${data.tnom || 'payslip'}.pdf`
|
||
};
|
||
|
||
} catch (error) {
|
||
logger.error('[PRINT-PAYSLIP] ❌ Ошибка:', error.message);
|
||
logger.error('[PRINT-PAYSLIP] Стек:', error.stack);
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|
||
|
||
// ✅ 5. Обработчик получения списка принтеров
|
||
ipcMain.handle('get-printers', async () => {
|
||
return [
|
||
{
|
||
name: 'GP-80250 Series',
|
||
displayName: 'GP-80250 Series',
|
||
isDefault: true,
|
||
status: 'ready'
|
||
}
|
||
];
|
||
});
|
||
|
||
// ✅ 6. Обработчик получения конфига
|
||
ipcMain.handle('get-app-config', async (event) => {
|
||
return appConfig;
|
||
});
|
||
|
||
// ============================================
|
||
// 🚀 СОЗДАНИЕ ГЛАВНОГО ОКНА
|
||
// ============================================
|
||
|
||
function createWindow() {
|
||
const preloadPath = path.join(__dirname, 'preload.js');
|
||
try {
|
||
fs.accessSync(preloadPath);
|
||
logger.debug(`[DEBUG] Preload script found at: ${preloadPath}`);
|
||
} catch (e) {
|
||
logger.error(`[FATAL] Preload script not found at: ${preloadPath}`);
|
||
app.quit();
|
||
return;
|
||
}
|
||
|
||
mainWindow = new BrowserWindow({
|
||
kiosk: true,
|
||
titleBarStyle: 'hidden',
|
||
alwaysOnTop: false,
|
||
webPreferences: {
|
||
preload: preloadPath,
|
||
contextIsolation: true,
|
||
nodeIntegration: false
|
||
},
|
||
});
|
||
|
||
mainWindow.loadURL(
|
||
isDev
|
||
? `${config.frontUrl}`
|
||
: `file://${path.join(__dirname, '../build/index.html')}`
|
||
);
|
||
|
||
mainWindow.on('close', (e) => {
|
||
if (!global.allowClose) {
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
mainWindow.on('minimize', (e) => {
|
||
e.preventDefault();
|
||
});
|
||
}
|
||
|
||
// ============================================
|
||
// 🚀 ЗАПУСК
|
||
// ============================================
|
||
|
||
app.whenReady().then(() => {
|
||
createWindow();
|
||
global.allowMinimize = false;
|
||
globalShortcut.register('Control+Alt+Backspace', () => {
|
||
global.allowMinimize = true;
|
||
if (mainWindow) mainWindow.minimize();
|
||
});
|
||
|
||
mainWindow.on('minimize', function (event) {
|
||
if (global.allowMinimize) {
|
||
global.allowMinimize = false;
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
setTimeout(() => {
|
||
if (mainWindow.isMinimized()) {
|
||
mainWindow.restore();
|
||
mainWindow.focus();
|
||
}
|
||
}, 10);
|
||
});
|
||
|
||
globalShortcut.register('Control+Alt+Enter', () => {
|
||
global.allowClose = true;
|
||
app.quit();
|
||
});
|
||
|
||
globalShortcut.register('F11', () => {});
|
||
});
|
||
|
||
app.on('window-all-closed', () => {
|
||
if (process.platform !== 'darwin') {
|
||
app.quit();
|
||
}
|
||
});
|
||
|
||
app.on('activate', () => {
|
||
if (mainWindow === null) {
|
||
createWindow();
|
||
}
|
||
});
|
||
|
||
app.on('will-quit', () => {
|
||
globalShortcut.unregisterAll();
|
||
}); |