Round 1 review minor + final reviewer minor 일괄:
- About dialog detail/clipboard 의 줄바꿈 → os.EOL (Windows Notepad 등에서 줄바꿈 정상)
- showMessageBox().then().catch(() => {}) — dialog reject (main crash 예외) silent
(tray.ts 가 logger 미import — minimal swallow 패턴 채택)
skip:
- nit: 트레이 메뉴 ordering ("정보" → "종료" 한 그룹) — 현재 패턴도 흔함, 호불호 영역
- nit: process.versions.electron ?? '?' dead branch — 안전 fallback 유지
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
157 lines
5.3 KiB
TypeScript
157 lines
5.3 KiB
TypeScript
import electron from 'electron';
|
|
import type { Tray as TrayType, MenuItemConstructorOptions } from 'electron';
|
|
import { platform, release, EOL } from 'node:os';
|
|
const { app, Tray, Menu, nativeImage, dialog, shell, clipboard } = electron;
|
|
|
|
function showAboutDialog(): void {
|
|
const version = app.getVersion();
|
|
const electronVersion = process.versions.electron ?? '?';
|
|
const nodeVersion = process.versions.node ?? '?';
|
|
const profileDir = app.getPath('userData');
|
|
// OS EOL 사용 — 클립보드 → Notepad 등에서 줄바꿈 정상.
|
|
const detail = [
|
|
`버전: ${version}`,
|
|
`Electron: ${electronVersion}`,
|
|
`Node: ${nodeVersion}`,
|
|
`OS: ${platform()} ${release()}`,
|
|
`데이터 위치: ${profileDir}`
|
|
].join(EOL);
|
|
void dialog.showMessageBox({
|
|
type: 'info',
|
|
title: 'Inkling 정보',
|
|
message: `Inkling ${version}`,
|
|
detail,
|
|
buttons: ['확인', '데이터 위치 열기', '정보 복사'],
|
|
defaultId: 0,
|
|
cancelId: 0
|
|
}).then((r) => {
|
|
if (r.response === 1) void shell.openPath(profileDir);
|
|
if (r.response === 2) clipboard.writeText(`Inkling ${version}${EOL}${detail}`);
|
|
}).catch(() => {
|
|
// dialog reject 는 일반 사용에서 발생 X — main process crash 등 예외 케이스 silent.
|
|
});
|
|
}
|
|
|
|
let tray: TrayType | null = null;
|
|
let _showInbox: () => void = () => {};
|
|
let _showCapture: () => void = () => {};
|
|
let _runBackup: () => void = () => {};
|
|
let _runExport: () => void = () => {};
|
|
let _runImport: () => void = () => {};
|
|
let _runSync: () => void = () => {};
|
|
let _runExportTelemetry: () => void = () => {};
|
|
let _runOllamaRecheck: () => void = () => {};
|
|
let _ollamaOk = true;
|
|
let _todayCount = 0;
|
|
let _runRetryAllFailed: () => void = () => {};
|
|
let _failedCount = 0;
|
|
let _runOpenOllamaSettings: () => void = () => {};
|
|
|
|
function buildMenu() {
|
|
const items: MenuItemConstructorOptions[] = [];
|
|
// F4-C: count > 0 시 비활성 라벨로 정체성 신호 노출. count = 0 시 메뉴를 자연스럽게 시작.
|
|
if (_todayCount > 0) {
|
|
items.push({ label: `오늘 ${_todayCount}번 잡아둠`, enabled: false });
|
|
items.push({ type: 'separator' });
|
|
}
|
|
items.push({ label: '보관한 메모 보기', click: _showInbox });
|
|
items.push({ label: '한 줄 적기', click: _showCapture });
|
|
items.push({ type: 'separator' });
|
|
items.push({ label: '지금 백업', click: _runBackup });
|
|
items.push({ label: '내보내기...', click: _runExport });
|
|
items.push({ label: '백업에서 복원...', click: _runImport });
|
|
items.push({ label: '지금 동기화', click: _runSync });
|
|
items.push({ label: '사용 로그 내보내기...', click: _runExportTelemetry });
|
|
items.push({
|
|
label: 'Ollama 재확인',
|
|
enabled: !_ollamaOk,
|
|
click: _runOllamaRecheck
|
|
});
|
|
items.push({
|
|
label: `지금 AI 처리 (실패 ${_failedCount}건)`,
|
|
enabled: _failedCount > 0,
|
|
click: _runRetryAllFailed
|
|
});
|
|
items.push({ label: 'Ollama 설정...', click: () => _runOpenOllamaSettings() });
|
|
if (app.isPackaged) {
|
|
const { openAtLogin } = app.getLoginItemSettings();
|
|
items.push({
|
|
label: '윈도우 시작 시 자동 실행',
|
|
type: 'checkbox',
|
|
checked: openAtLogin,
|
|
click: (item) => {
|
|
app.setLoginItemSettings({
|
|
openAtLogin: item.checked,
|
|
args: ['--hidden']
|
|
});
|
|
}
|
|
});
|
|
items.push({ type: 'separator' });
|
|
} else {
|
|
items.push({ type: 'separator' });
|
|
}
|
|
items.push({ label: 'Inkling 정보...', click: showAboutDialog });
|
|
items.push({ label: '종료', click: () => { app.isQuitting = true; app.quit(); } });
|
|
return Menu.buildFromTemplate(items);
|
|
}
|
|
|
|
export function createTray(
|
|
showInbox: () => void,
|
|
showCapture: () => void,
|
|
runBackup: () => void,
|
|
runExport: () => void,
|
|
runImport: () => void,
|
|
runSync: () => void,
|
|
runExportTelemetry: () => void,
|
|
runOllamaRecheck: () => void,
|
|
runRetryAllFailed: () => void,
|
|
runOpenOllamaSettings: () => void
|
|
): TrayType {
|
|
_showInbox = showInbox;
|
|
_showCapture = showCapture;
|
|
_runBackup = runBackup;
|
|
_runExport = runExport;
|
|
_runImport = runImport;
|
|
_runSync = runSync;
|
|
_runExportTelemetry = runExportTelemetry;
|
|
_runOllamaRecheck = runOllamaRecheck;
|
|
_runRetryAllFailed = runRetryAllFailed;
|
|
_runOpenOllamaSettings = runOpenOllamaSettings;
|
|
const icon = nativeImage.createEmpty();
|
|
tray = new Tray(icon);
|
|
tray.setToolTip(`Inkling — 오늘 ${_todayCount}`);
|
|
tray.setContextMenu(buildMenu());
|
|
tray.on('click', showInbox);
|
|
return tray;
|
|
}
|
|
|
|
/**
|
|
* F4-C 환경 앵커 — tooltip + 메뉴 첫 항목을 오늘 캡처 수로 갱신.
|
|
* `src/main/index.ts` 가 60s interval / AiWorker onUpdate 시점에 호출.
|
|
*/
|
|
export function refreshTray(todayCount: number): void {
|
|
_todayCount = todayCount;
|
|
if (tray === null) return;
|
|
tray.setToolTip(`Inkling — 오늘 ${todayCount}`);
|
|
tray.setContextMenu(buildMenu());
|
|
}
|
|
|
|
/**
|
|
* v0.2.3 #1 — Ollama 상태가 변할 때 main 의 health.onUpdate 가 호출.
|
|
* 메뉴의 "Ollama 재확인" 활성/비활성 상태 갱신.
|
|
*/
|
|
export function refreshTrayOllama(ok: boolean): void {
|
|
_ollamaOk = ok;
|
|
if (tray === null) return;
|
|
tray.setContextMenu(buildMenu());
|
|
}
|
|
|
|
/**
|
|
* v0.2.3 #2 — AiWorker.onUpdate 시 실패 카운트 변하면 메뉴 라벨 + enabled 갱신.
|
|
*/
|
|
export function refreshTrayFailedCount(count: number): void {
|
|
_failedCount = count;
|
|
if (tray === null) return;
|
|
tray.setContextMenu(buildMenu());
|
|
}
|