refactor(v026): #4+#23+#26+#27 TrayCallbacks 객체화 + state 통합
createTray(callbacks: TrayCallbacks) 1-arg signature. 기존 10 positional 폐기.
TrayState 통합 (ollamaOk, todayCount, failedCount) — refreshTray({...partial})
1개 setter 로 일원화.
기존 refreshTrayOllama / refreshTrayFailedCount export 제거 — 호출자 모두
refreshTray({ ollamaOk: ... }) / refreshTray({ failedCount: ... }) 로 migrate.
module-scoped 개별 state 변수 (_failedCount 등) 제거.
backlog 4건 일괄: #4 (positional 폭주) / #23 (8 callbacks) / #26 (10 callbacks) /
#27 (refreshTrayFailedCount singleton). 다음 menu item 추가 시 callback
프로퍼티 추가만 — readability blocker 해소.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import { createInboxWindow, getInboxWindow } from './windows/inboxWindow.js';
|
||||
import {
|
||||
createQuickCaptureWindow, showQuickCapture, getQuickCaptureWindow
|
||||
} from './windows/quickCaptureWindow.js';
|
||||
import { createTray, refreshTray, refreshTrayOllama, refreshTrayFailedCount } from './tray.js';
|
||||
import { createTray, refreshTray } from './tray.js';
|
||||
import { MediaGc } from './services/MediaGc.js';
|
||||
import { BackupService } from './services/BackupService.js';
|
||||
import { ExportService } from './services/ExportService.js';
|
||||
@@ -120,7 +120,7 @@ app.whenReady().then(async () => {
|
||||
onUpdate: (status) => {
|
||||
logger.info('ai.health', { ...status } as Record<string, unknown>);
|
||||
pushOllamaStatus(getInboxWindow, status);
|
||||
refreshTrayOllama(status.ok);
|
||||
refreshTray({ ollamaOk: status.ok });
|
||||
},
|
||||
onTelemetry: (ev) => {
|
||||
if (ev.kind === 'ollama_unreachable') {
|
||||
@@ -138,8 +138,7 @@ app.whenReady().then(async () => {
|
||||
onUpdate: (note) => {
|
||||
pushNoteUpdated(getInboxWindow, note);
|
||||
// F4-C: AI 처리 완료 = 새 캡처가 inbox 에 합류한 시점, tray 도 즉시 갱신.
|
||||
refreshTray(repo.countToday());
|
||||
refreshTrayFailedCount(repo.countFailed());
|
||||
refreshTray({ todayCount: repo.countToday(), failedCount: repo.countFailed() });
|
||||
},
|
||||
logger,
|
||||
telemetry
|
||||
@@ -223,10 +222,10 @@ app.whenReady().then(async () => {
|
||||
});
|
||||
});
|
||||
|
||||
createTray(
|
||||
() => createInboxWindow(),
|
||||
() => showQuickCapture(),
|
||||
async () => {
|
||||
createTray({
|
||||
showInbox: () => createInboxWindow(),
|
||||
showCapture: () => showQuickCapture(),
|
||||
runBackup: async () => {
|
||||
try {
|
||||
const r = await backup.runDaily();
|
||||
new Notification({
|
||||
@@ -245,7 +244,7 @@ app.whenReady().then(async () => {
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
runExport: async () => {
|
||||
const win = getInboxWindow();
|
||||
const dialogOpts: Electron.OpenDialogOptions = {
|
||||
title: '내보낼 폴더 선택',
|
||||
@@ -279,7 +278,7 @@ app.whenReady().then(async () => {
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
runImport: async () => {
|
||||
const win = getInboxWindow();
|
||||
const dirOpts: Electron.OpenDialogOptions = {
|
||||
title: '복원할 백업 폴더 선택',
|
||||
@@ -341,7 +340,7 @@ app.whenReady().then(async () => {
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
runSync: async () => {
|
||||
// runSync — 트레이 "지금 동기화"
|
||||
try {
|
||||
const r = await syncSvc.sync();
|
||||
@@ -364,7 +363,7 @@ app.whenReady().then(async () => {
|
||||
new Notification({ title: 'Inkling', body: '동기화를 완료하지 못했습니다.', silent: true }).show();
|
||||
}
|
||||
},
|
||||
/* runExportTelemetry */ async () => {
|
||||
runExportTelemetry: async () => {
|
||||
const win = getInboxWindow();
|
||||
const dialogOpts: Electron.OpenDialogOptions = {
|
||||
title: '사용 로그를 내보낼 폴더 선택',
|
||||
@@ -393,21 +392,20 @@ app.whenReady().then(async () => {
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
/* runOllamaRecheck */ () => { void health.runOnce({ manual: true }); },
|
||||
/* runRetryAllFailed */ () => { void capture.retryAllFailed(); },
|
||||
/* runOpenOllamaSettings */ () => {
|
||||
runOllamaRecheck: () => { void health.runOnce({ manual: true }); },
|
||||
runRetryAllFailed: () => { void capture.retryAllFailed(); },
|
||||
runOpenOllamaSettings: () => {
|
||||
const win = getInboxWindow();
|
||||
if (win) win.webContents.send('inbox:openOllamaSettings');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// F4-C 환경 앵커 — tray tooltip + 메뉴 첫 항목을 오늘 KST 캡처 수로 갱신.
|
||||
// 초기 1회 + 60s interval. AiWorker.onUpdate 도 별도 갱신 트리거.
|
||||
// cleanup 은 위 통합 before-quit 핸들러에서 처리.
|
||||
refreshTray(repo.countToday());
|
||||
refreshTrayFailedCount(repo.countFailed());
|
||||
refreshTray({ todayCount: repo.countToday(), failedCount: repo.countFailed() });
|
||||
trayInterval = setInterval(() => {
|
||||
refreshTray(repo.countToday());
|
||||
refreshTray({ todayCount: repo.countToday() });
|
||||
}, 60_000);
|
||||
|
||||
app.on('activate', () => {
|
||||
|
||||
145
src/main/tray.ts
145
src/main/tray.ts
@@ -32,47 +32,66 @@ function showAboutDialog(): void {
|
||||
});
|
||||
}
|
||||
|
||||
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 = () => {};
|
||||
/**
|
||||
* v0.2.6 C2 — 트레이 메뉴 콜백 묶음. createTray 가 1-arg 로 받음.
|
||||
*/
|
||||
export interface TrayCallbacks {
|
||||
showInbox: () => void;
|
||||
showCapture: () => void;
|
||||
runBackup: () => void;
|
||||
runExport: () => void;
|
||||
runImport: () => void;
|
||||
runSync: () => void;
|
||||
runExportTelemetry: () => void;
|
||||
runOllamaRecheck: () => void;
|
||||
runRetryAllFailed: () => void;
|
||||
runOpenOllamaSettings: () => void;
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
/**
|
||||
* v0.2.6 C3 — 메뉴 라벨/활성화에 영향 주는 reactive state. refreshTray() 로 partial 갱신.
|
||||
*/
|
||||
export interface TrayState {
|
||||
ollamaOk: boolean;
|
||||
todayCount: number;
|
||||
failedCount: number;
|
||||
}
|
||||
|
||||
let tray: TrayType | null = null;
|
||||
let _callbacks: TrayCallbacks | null = null;
|
||||
let _state: TrayState = { ollamaOk: true, todayCount: 0, failedCount: 0 };
|
||||
|
||||
function buildMenu(): electron.Menu {
|
||||
const items: MenuItemConstructorOptions[] = [];
|
||||
const cb = _callbacks;
|
||||
if (!cb) {
|
||||
// createTray 호출 전이면 빈 메뉴 (defensive)
|
||||
return Menu.buildFromTemplate([{ label: '로딩 중...', enabled: false }]);
|
||||
}
|
||||
// F4-C: count > 0 시 비활성 라벨로 정체성 신호 노출. count = 0 시 메뉴를 자연스럽게 시작.
|
||||
if (_todayCount > 0) {
|
||||
items.push({ label: `오늘 ${_todayCount}번 잡아둠`, enabled: false });
|
||||
if (_state.todayCount > 0) {
|
||||
items.push({ label: `오늘 ${_state.todayCount}번 잡아둠`, enabled: false });
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
items.push({ label: '보관한 메모 보기', click: _showInbox });
|
||||
items.push({ label: '한 줄 적기', click: _showCapture });
|
||||
items.push({ label: '보관한 메모 보기', click: cb.showInbox });
|
||||
items.push({ label: '한 줄 적기', click: cb.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: '지금 백업', click: cb.runBackup });
|
||||
items.push({ label: '내보내기...', click: cb.runExport });
|
||||
items.push({ label: '백업에서 복원...', click: cb.runImport });
|
||||
items.push({ label: '지금 동기화', click: cb.runSync });
|
||||
items.push({ label: '사용 로그 내보내기...', click: cb.runExportTelemetry });
|
||||
items.push({
|
||||
label: 'Ollama 재확인',
|
||||
enabled: !_ollamaOk,
|
||||
click: _runOllamaRecheck
|
||||
enabled: !_state.ollamaOk,
|
||||
click: cb.runOllamaRecheck
|
||||
});
|
||||
items.push({
|
||||
label: `지금 AI 처리 (실패 ${_failedCount}건)`,
|
||||
enabled: _failedCount > 0,
|
||||
click: _runRetryAllFailed
|
||||
label: `지금 AI 처리 (실패 ${_state.failedCount}건)`,
|
||||
enabled: _state.failedCount > 0,
|
||||
click: cb.runRetryAllFailed
|
||||
});
|
||||
items.push({ label: 'Ollama 설정...', click: () => _runOpenOllamaSettings() });
|
||||
items.push({ label: 'Ollama 설정...', click: cb.runOpenOllamaSettings });
|
||||
if (app.isPackaged) {
|
||||
// v0.2.6 #45 — args 명시 전달로 openAtLogin 비교 정확도. setLoginItemSettings 가
|
||||
// args 와 함께 LoginItem 등록하므로 read 시도 같은 args 로 비교해야 매치됨.
|
||||
@@ -97,62 +116,32 @@ function buildMenu() {
|
||||
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;
|
||||
/**
|
||||
* v0.2.6 C2 — 1-arg createTray. 기존 10 positional 폐기.
|
||||
*/
|
||||
export function createTray(callbacks: TrayCallbacks): TrayType {
|
||||
_callbacks = callbacks;
|
||||
const icon = nativeImage.createEmpty();
|
||||
tray = new Tray(icon);
|
||||
tray.setToolTip(`Inkling — 오늘 ${_todayCount}`);
|
||||
tray.setToolTip(`Inkling — 오늘 ${_state.todayCount}`);
|
||||
tray.setContextMenu(buildMenu());
|
||||
tray.on('click', showInbox);
|
||||
tray.on('click', callbacks.showInbox);
|
||||
return tray;
|
||||
}
|
||||
|
||||
/**
|
||||
* F4-C 환경 앵커 — tooltip + 메뉴 첫 항목을 오늘 캡처 수로 갱신.
|
||||
* `src/main/index.ts` 가 60s interval / AiWorker onUpdate 시점에 호출.
|
||||
* v0.2.6 C3 — 통합 state 갱신. partial 으로 받아 _state merge + 메뉴 재빌드.
|
||||
*
|
||||
* Replaces: refreshTrayOllama(ok), refreshTrayFailedCount(count), 기존 refreshTray(todayCount).
|
||||
*
|
||||
* 호출 예:
|
||||
* refreshTray({ todayCount: 5 });
|
||||
* refreshTray({ ollamaOk: false });
|
||||
* refreshTray({ failedCount: 2 });
|
||||
*/
|
||||
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;
|
||||
export function refreshTray(state: Partial<TrayState>): void {
|
||||
_state = { ..._state, ...state };
|
||||
if (tray === null) return;
|
||||
tray.setToolTip(`Inkling — 오늘 ${_state.todayCount}`);
|
||||
tray.setContextMenu(buildMenu());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user