feat(import): wire ImportService — tray '백업에서 복원...' + preview dialog
Tray gets 5th callback. Directory chooser → preview (count of new/skip/forked + media) → confirm message box → run. Slice §1.1 copy policy preserved (no '실패'/'끊김'). Notification on success/failure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import { createTray } from './tray.js';
|
||||
import { MediaGc } from './services/MediaGc.js';
|
||||
import { BackupService } from './services/BackupService.js';
|
||||
import { ExportService } from './services/ExportService.js';
|
||||
import { ImportService } from './services/ImportService.js';
|
||||
|
||||
const HIDDEN_ARG = '--hidden';
|
||||
const startedHidden = process.argv.includes(HIDDEN_ARG);
|
||||
@@ -104,6 +105,7 @@ app.whenReady().then(async () => {
|
||||
void gc.run().then((r) => logger.info('media.gc', { ...r } as Record<string, unknown>));
|
||||
|
||||
const exportSvc = new ExportService(repo, store);
|
||||
const importSvc = new ImportService(repo, store);
|
||||
|
||||
const backup = new BackupService(db, join(paths.profileDir, 'backups'));
|
||||
void backup.runDaily()
|
||||
@@ -179,6 +181,68 @@ app.whenReady().then(async () => {
|
||||
silent: true
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
const win = getInboxWindow();
|
||||
const dirOpts: Electron.OpenDialogOptions = {
|
||||
title: '복원할 백업 폴더 선택',
|
||||
message: 'F5 export 형식의 폴더를 선택하세요. notes/ 하위의 마크다운 파일이 적재됩니다.',
|
||||
buttonLabel: '여기서 복원',
|
||||
properties: ['openDirectory']
|
||||
};
|
||||
const dirResult = win
|
||||
? await dialog.showOpenDialog(win, dirOpts)
|
||||
: await dialog.showOpenDialog(dirOpts);
|
||||
if (dirResult.canceled || dirResult.filePaths.length === 0) return;
|
||||
const sourceDir = dirResult.filePaths[0]!;
|
||||
let plan;
|
||||
try {
|
||||
plan = await importSvc.preview(sourceDir);
|
||||
} catch (e) {
|
||||
logger.warn('import.preview.failed', { reason: String(e) });
|
||||
new Notification({
|
||||
title: 'Inkling',
|
||||
body: '백업 폴더를 읽지 못했습니다.',
|
||||
silent: true
|
||||
}).show();
|
||||
return;
|
||||
}
|
||||
const detail = `총 ${plan.total}개 노트\n · 신규 ${plan.newCount}개\n · 동일 (스킵) ${plan.unchangedCount}개\n · 충돌→새 id (${plan.forkedCount}개, raw_text 보존)\n\n이미지 ${plan.mediaCount}개 복사 예정.`;
|
||||
const confirmOpts: Electron.MessageBoxOptions = {
|
||||
type: 'question',
|
||||
buttons: ['복원', '취소'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
title: 'Inkling 복원',
|
||||
message: '복원 미리보기',
|
||||
detail
|
||||
};
|
||||
const confirm = win
|
||||
? await dialog.showMessageBox(win, confirmOpts)
|
||||
: await dialog.showMessageBox(confirmOpts);
|
||||
if (confirm.response !== 0) return;
|
||||
try {
|
||||
const r = await importSvc.run(sourceDir);
|
||||
logger.info('import.done', {
|
||||
total: r.total,
|
||||
new: r.newCount,
|
||||
unchanged: r.unchangedCount,
|
||||
forked: r.forkedCount,
|
||||
media: r.mediaCount
|
||||
});
|
||||
new Notification({
|
||||
title: 'Inkling',
|
||||
body: `복원 완료 — 신규 ${r.newCount}개, 스킵 ${r.unchangedCount}개, 충돌 ${r.forkedCount}개`,
|
||||
silent: true
|
||||
}).show();
|
||||
} catch (e) {
|
||||
logger.warn('import.run.failed', { reason: String(e) });
|
||||
new Notification({
|
||||
title: 'Inkling',
|
||||
body: '복원을 완료하지 못했습니다.',
|
||||
silent: true
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,14 +8,16 @@ function buildMenu(
|
||||
showInbox: () => void,
|
||||
showCapture: () => void,
|
||||
runBackup: () => void,
|
||||
runExport: () => void
|
||||
runExport: () => void,
|
||||
runImport: () => void
|
||||
) {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ label: '구출한 메모 보기', click: showInbox },
|
||||
{ label: '기억 구출하기', click: showCapture },
|
||||
{ type: 'separator' },
|
||||
{ label: '지금 백업', click: runBackup },
|
||||
{ label: '내보내기...', click: runExport }
|
||||
{ label: '내보내기...', click: runExport },
|
||||
{ label: '백업에서 복원...', click: runImport }
|
||||
];
|
||||
if (app.isPackaged) {
|
||||
const { openAtLogin } = app.getLoginItemSettings();
|
||||
@@ -42,12 +44,13 @@ export function createTray(
|
||||
showInbox: () => void,
|
||||
showCapture: () => void,
|
||||
runBackup: () => void,
|
||||
runExport: () => void
|
||||
runExport: () => void,
|
||||
runImport: () => void
|
||||
): TrayType {
|
||||
const icon = nativeImage.createEmpty();
|
||||
tray = new Tray(icon);
|
||||
tray.setToolTip('Inkling');
|
||||
tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup, runExport));
|
||||
tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup, runExport, runImport));
|
||||
tray.on('click', showInbox);
|
||||
return tray;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user