Skip to content

Commit 6059540

Browse files
committed
refactor: maximized readability, zero logic drift
1 parent 38994c5 commit 6059540

6 files changed

Lines changed: 302 additions & 300 deletions

File tree

main.js

Lines changed: 111 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const { app, BrowserWindow, BrowserView, ipcMain, shell, Notification, Menu } =
22
const path = require('path');
33
const fs = require('fs');
44
const pkg = require('./package.json');
5+
const CONSTANTS = require('./src/constants');
6+
57
const APP_VERSION = pkg.version;
68

79
let mainWindow;
@@ -14,20 +16,58 @@ app.userAgentFallback = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/5
1416
function getConfig() {
1517
const CONFIG_PATH = path.join(app.getPath('userData'), 'config.json');
1618
const defaultConfig = { outputDir: '', ids: '', folderStructure: 'by_id', includeCompanyName: false };
19+
if (!fs.existsSync(CONFIG_PATH)) return defaultConfig;
20+
1721
try {
18-
if (fs.existsSync(CONFIG_PATH)) {
19-
return { ...defaultConfig, ...JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) };
20-
}
21-
} catch (e) {}
22-
return defaultConfig;
22+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
23+
return { ...defaultConfig, ...JSON.parse(data) };
24+
} catch (e) {
25+
console.error('Failed to read config:', e);
26+
return defaultConfig;
27+
}
2328
}
2429

2530
function saveConfig(config) {
2631
const CONFIG_PATH = path.join(app.getPath('userData'), 'config.json');
2732
try {
2833
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
2934
return true;
30-
} catch (e) { return false; }
35+
} catch (e) {
36+
console.error('Failed to save config:', e);
37+
return false;
38+
}
39+
}
40+
41+
const handleDevToolsShortcut = (targetWebContents) => (event, input) => {
42+
if (input.type !== 'keyDown') return;
43+
44+
const isF12 = input.key === 'F12';
45+
const isCtrlShiftI = input.key.toLowerCase() === 'i' && (input.control || input.meta) && input.shift;
46+
if (isF12 || isCtrlShiftI) {
47+
targetWebContents.toggleDevTools();
48+
event.preventDefault();
49+
}
50+
};
51+
52+
function createBrowserView() {
53+
if (!mainWindow || mainWindow.isDestroyed()) return;
54+
55+
browserView = new BrowserView({
56+
webPreferences: { nodeIntegration: false, contextIsolation: true },
57+
});
58+
59+
mainWindow.setBrowserView(browserView);
60+
61+
const updateBounds = () => {
62+
if (!mainWindow || mainWindow.isDestroyed()) return;
63+
const { width, height } = mainWindow.getContentBounds();
64+
browserView.setBounds({ x: 450, y: 0, width: width - 450, height: height });
65+
};
66+
67+
updateBounds();
68+
mainWindow.on('resize', updateBounds);
69+
browserView.webContents.on('before-input-event', handleDevToolsShortcut(browserView.webContents));
70+
browserView.webContents.loadURL(CONSTANTS.URLS.LOGIN);
3171
}
3272

3373
function createWindow() {
@@ -48,40 +88,9 @@ function createWindow() {
4888
});
4989

5090
mainWindow.loadFile(path.join(__dirname, 'src/renderer/index.html'));
51-
52-
const handleDevToolsShortcut = (targetWebContents) => (event, input) => {
53-
if (input.type === 'keyDown') {
54-
const isF12 = input.key === 'F12';
55-
const isCtrlShiftI = input.key.toLowerCase() === 'i' && (input.control || input.meta) && input.shift;
56-
if (isF12 || isCtrlShiftI) {
57-
targetWebContents.toggleDevTools();
58-
event.preventDefault();
59-
}
60-
}
61-
};
62-
6391
mainWindow.webContents.on('before-input-event', handleDevToolsShortcut(mainWindow.webContents));
6492

65-
setTimeout(() => {
66-
if (!mainWindow || mainWindow.isDestroyed()) return;
67-
const CONSTANTS = require('./src/constants');
68-
69-
browserView = new BrowserView({
70-
webPreferences: { nodeIntegration: false, contextIsolation: true },
71-
});
72-
73-
mainWindow.setBrowserView(browserView);
74-
const updateBounds = () => {
75-
if (!mainWindow || mainWindow.isDestroyed()) return;
76-
const { width, height } = mainWindow.getContentBounds();
77-
browserView.setBounds({ x: 450, y: 0, width: width - 450, height: height });
78-
};
79-
80-
updateBounds();
81-
mainWindow.on('resize', updateBounds);
82-
browserView.webContents.on('before-input-event', handleDevToolsShortcut(browserView.webContents));
83-
browserView.webContents.loadURL(CONSTANTS.URLS.LOGIN);
84-
}, 400);
93+
setTimeout(createBrowserView, 400);
8594

8695
app.on('select-client-certificate', (event, webContents, url, list, callback) => {
8796
event.preventDefault();
@@ -102,44 +111,73 @@ app.on('window-all-closed', () => {
102111
if (process.platform !== 'darwin') app.quit();
103112
});
104113

105-
ipcMain.handle('get-app-version', () => {
106-
return APP_VERSION;
107-
});
114+
ipcMain.handle('get-app-version', () => APP_VERSION);
108115

109-
ipcMain.handle('start-voting', async (event, { ids, outputDir, folderStructure, includeCompanyName }) => {
116+
ipcMain.handle('start-voting', async (event, params) => {
117+
const { ids, outputDir, folderStructure, includeCompanyName } = params;
110118
stopRequested = false;
111119
const automation = require('./src/automation/main_flow');
120+
121+
const sendLog = (msg) => {
122+
if (!mainWindow || mainWindow.isDestroyed()) return;
123+
mainWindow.webContents.send('log', String(msg));
124+
};
125+
126+
const sendProgress = (progress) => {
127+
if (!mainWindow || mainWindow.isDestroyed()) return;
128+
mainWindow.webContents.send('progress', JSON.parse(JSON.stringify(progress)));
129+
130+
const { id, screenshot } = progress;
131+
if (!id || id.total <= 0) return;
132+
133+
const base = id.current - (screenshot && screenshot.total > 0 ? 1 : 0);
134+
const screenshotProgress = screenshot ? screenshot.current / screenshot.total : 0;
135+
const percent = Math.floor(((base + screenshotProgress) / id.total) * 100);
136+
const safePercent = Math.min(100, Math.max(0, percent));
137+
138+
mainWindow.setTitle(`(${safePercent}%) 股東會投票幫手`);
139+
};
140+
112141
try {
113-
const stats = await automation.run(browserView.webContents, ids, (msg) => {
114-
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('log', String(msg));
115-
}, (progress) => {
116-
if (mainWindow && !mainWindow.isDestroyed()) {
117-
mainWindow.webContents.send('progress', JSON.parse(JSON.stringify(progress)));
118-
const { id, screenshot } = progress;
119-
let percent = 0;
120-
if (id && id.total > 0) {
121-
let base = id.current - (screenshot && screenshot.total > 0 ? 1 : 0);
122-
percent = Math.floor(((base + (screenshot ? screenshot.current / screenshot.total : 0)) / id.total) * 100);
123-
}
124-
mainWindow.setTitle(`(${Math.min(100, Math.max(0, percent))}%) 股東會投票幫手`);
125-
}
126-
}, () => stopRequested, outputDir, folderStructure, includeCompanyName);
127-
128-
if (mainWindow && !mainWindow.isDestroyed()) {
129-
mainWindow.setTitle('股東會投票幫手');
130-
if (stats) mainWindow.webContents.send('log', `[系統] 完成。累計投票: ${stats.voted},累計截圖: ${stats.screenshoted}`);
131-
if (!mainWindow.isFocused() && Notification.isSupported()) {
132-
new Notification({ title: '投票完成', body: '所有作業已結束。', icon: path.join(__dirname, 'assets/icons/icon.png') }).show();
133-
}
142+
const stats = await automation.run(
143+
browserView.webContents,
144+
ids,
145+
sendLog,
146+
sendProgress,
147+
() => stopRequested,
148+
outputDir,
149+
folderStructure,
150+
includeCompanyName
151+
);
152+
153+
if (!mainWindow || mainWindow.isDestroyed()) return { success: true };
154+
155+
mainWindow.setTitle('股東會投票幫手');
156+
if (stats) {
157+
sendLog(`[系統] 完成。累計投票: ${stats.voted},累計截圖: ${stats.screenshoted}`);
134158
}
159+
160+
if (!mainWindow.isFocused() && Notification.isSupported()) {
161+
new Notification({
162+
title: '投票完成',
163+
body: '所有作業已結束。',
164+
icon: path.join(__dirname, 'assets/icons/icon.png'),
165+
}).show();
166+
}
167+
135168
return { success: true };
136169
} catch (error) {
137-
if (mainWindow && !mainWindow.isDestroyed()) {
138-
mainWindow.setTitle('股東會投票幫手');
139-
if (!mainWindow.isFocused() && Notification.isSupported()) {
140-
new Notification({ title: '投票錯誤', body: error.message, icon: path.join(__dirname, 'assets/icons/icon.png') }).show();
141-
}
170+
if (!mainWindow || mainWindow.isDestroyed()) return { success: false, error: error.message };
171+
172+
mainWindow.setTitle('股東會投票幫手');
173+
if (!mainWindow.isFocused() && Notification.isSupported()) {
174+
new Notification({
175+
title: '投票錯誤',
176+
body: error.message,
177+
icon: path.join(__dirname, 'assets/icons/icon.png'),
178+
}).show();
142179
}
180+
143181
return { success: false, error: error.message };
144182
}
145183
});
@@ -152,6 +190,7 @@ ipcMain.handle('select-directory', async () => {
152190

153191
ipcMain.handle('get-config', async () => getConfig());
154192
ipcMain.handle('save-config', async (event, config) => saveConfig(config));
193+
155194
ipcMain.handle('open-about', async () => {
156195
const aboutWindow = new BrowserWindow({
157196
width: 400,
@@ -174,4 +213,7 @@ ipcMain.handle('open-external', async (event, url) => {
174213
return { success: true };
175214
});
176215

177-
ipcMain.handle('stop-voting', () => { stopRequested = true; return { success: true }; });
216+
ipcMain.handle('stop-voting', () => {
217+
stopRequested = true;
218+
return { success: true };
219+
});

0 commit comments

Comments
 (0)