const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const path = require('path'); const fs = require('fs'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 820, minWidth: 900, minHeight: 600, titleBarStyle: 'hiddenInset', backgroundColor: '#0f0f17', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, show: false, }); mainWindow.loadFile(path.join(__dirname, 'index.html')); mainWindow.once('ready-to-show', () => { mainWindow.show(); }); } app.whenReady().then(createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); // ── IPC 핸들러 ────────────────────────────────────── ipcMain.handle('select-folder', async () => { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], title: '이미지 폴더 선택', }); if (result.canceled) return null; return result.filePaths[0]; }); ipcMain.handle('list-images', async (_event, folderPath) => { const EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp', '.heic', '.heif']); try { const files = fs.readdirSync(folderPath) .filter(f => EXTS.has(path.extname(f).toLowerCase())) .map(f => { const fullPath = path.join(folderPath, f); const stat = fs.statSync(fullPath); return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size }; }) .sort((a, b) => a.name.localeCompare(b.name, 'ko')); return files; } catch (e) { return []; } }); ipcMain.handle('read-image', async (_event, filePath) => { try { const data = fs.readFileSync(filePath); const ext = path.extname(filePath).toLowerCase().replace('.', ''); const mime = (ext === 'jpg' || ext === 'jpeg') ? 'jpeg' : ext === 'png' ? 'png' : ext === 'gif' ? 'gif' : ext === 'webp' ? 'webp' : 'jpeg'; return `data:image/${mime};base64,${data.toString('base64')}`; } catch (e) { return null; } }); ipcMain.handle('rename-files', async (_event, { folderPath, files, prefix, digits }) => { const errors = []; const pad = (n) => String(n + 1).padStart(digits, '0'); // 1단계: 임시 이름으로 이동 (충돌 방지) const tmpMap = []; for (let i = 0; i < files.length; i++) { const src = files[i].path; const ext = path.extname(files[i].name).toLowerCase(); const tmp = path.join(folderPath, `__tmp_rename_${i}_${Date.now()}${ext}`); try { fs.renameSync(src, tmp); tmpMap.push({ tmp, ext, idx: i }); } catch (e) { errors.push(`${files[i].name}: ${e.message}`); } } // 2단계: 최종 이름으로 이동 const renamed = []; for (const { tmp, ext, idx } of tmpMap) { const newName = `${prefix}_${pad(idx)}${ext}`; const dest = path.join(folderPath, newName); try { fs.renameSync(tmp, dest); renamed.push({ oldPath: tmp, newPath: dest, newName }); } catch (e) { errors.push(`${newName}: ${e.message}`); } } return { renamed, errors }; }); ipcMain.handle('reveal-folder', async (_event, folderPath) => { shell.openPath(folderPath); });