1st commit
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user