Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit e8959d9

Browse files
authored
feat: 트레이 아이콘 모드에 따라 변경 #patch (#61)
* changeTrayTitle 인터페이스 추가 * 타이머 모드에 따라 트레이 아이콘 변경
1 parent f34f3a2 commit e8959d9

File tree

4 files changed

+53
-4
lines changed

4 files changed

+53
-4
lines changed

src/main/main.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,18 @@ const createWindow = () => {
5656
};
5757

5858
const trayIconMap: Record<string, string> = {
59-
cat: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEuSURBVHgB1ZRNTsMwEIXfmBCxzBF8A1hCYeFIlC1HCNyAI7QnQNygR8i+IFcCdd0bEG7QJVRNhiENKM4PoemGvoXljD3fOOMZA/9dVDXw9MIAmYHnTyicJU1ObE811uoGjJiu5otWID+d34H5vlhJoPywCs1hmbIC0xuDCmn4Mvte99zQuC7NxXFl2ZoQ74XtSCZlWB6YIxlbgMRaNsOBpqtXHBbfqUKXund0S7cDGQF2VPWEPYAcNALZDk7QT1ouLqgBsWaDfgqQfpg6ELRz/lygkkLuq4ySBqAfy7jEtpKOKrffD1BabCll84DtNXb5FfHj2UTMEf4ixpiG89GvwHzfdDCSs0dOz7pe8jcHt3T5HNeX2oJvXhUjUKlPOhbLV37f5HWJ4XmLPEV7qU80zGLwaU45uQAAAABJRU5ErkJggg',
59+
default:
60+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACoSURBVHgB7ZPhCcIwEIVfigPUDTKCG5gNWydQJ+gIukHdwBWywfmKUdIj1Bz2Zz84ynH3vhJCgA0R6eTNmdUW5i3rlna6X7Igc8ZcmmSj2gm5Y6ecXvUH1iSI/MY090sZLSzhYaD5J1zKNFgZLTzCzizj8ma6MtiJzrn9mkJQ+PXoI99h55E3WniFndPilKcepJ4BNXCxr5D1sMCAZ13U232mnwVsfHgBY9vO9YovS8sAAAAASUVORK5CYII=',
6061
focus:
61-
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAF5SURBVHgBxZRBTsJAGIXfTEkJEWNJ3LiyG1dugJWCizbGvTcQb4AnQE+AnoB7iKRdaHRXbiBx44qAG40F+jttLdgMbYGNL5nm78ybN99kJgP8l+i+VibL0LN8HCuIevW2cDqYTTpZXpYaZBmaCLFEVV50cpOdPdrYiNCbtGJhAQK1sAlhSOeOls9it+z06QprEU6n5cQxoiZ1j5vrBeamA6SJsTZ1TwypW1rc3yqgMdMe0EPN37KGZI2hqBXfu5SQrCMdnuuIZoWrsz7SpYUHl0BIvVoHhAbWlaKWBOU4RhjQbRLmy3PPo3KxZY8biRMKe8D2AZIDoUdlDlkqVUSr/rqLwMiRPYz6MiH3bMnoU0VhQXh1OamSlwOZ+TIQXztm3DmUJxelwLvEawNldinOfT4IpkISzy9q36uo17Hhvz8BJffMeejnmxzoDiO3DR5c6nEsAwkSd7IBXrjAbt3A1n7Y+fUODJ9tfH/cpD1hmSKnqdNrR1/F+wOB4H1F8tsNAAAAAABJRU5ErkJggg==',
62+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADQSURBVHgBzZMNDcMgEEZPQiVUQiVMQiUgoQ5WCXPQOZgEJEwCEirhdmRH9o1Qftol60tIyAGPuwsQ/QtmHugXiKiTsfAbQ0dQ2ZM/WDoCZIZMtAfNLsUqo6dW5NCFt3H+wlZhz3nq+qkio3NXkE4lWQeSpUK4ZkuXxevGISPj1pwlf7+5wAjr98T6khPGzBr3GfreYkuqhLjZaSw8H6fC+DmZnHCON0aXzBqzEOtzQiwplU3IesILssimQaWhZ4iFSsoyzBTmo5b44D1/+HS8AOKXTqS+H4j4AAAAAElFTkSuQmCC',
63+
'focus-exceed':
64+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEfSURBVHgB1ZExbgIxEEXHq0hRuhyBI1Cm3Ch1JI7gNEkZ5wLBcAGSFiGxnIAjLB0l3ABuABWCBjMWY2lkeQdTIb402tnx7POfWYBbyTnXzulTGaBnfAwwNMaHUqpyv59dgKM5E4pK9YY/WUCC1RjB3Qzs1xIPTOSrp/pD67MCZA0YzKuExyeTuPo7ZMUFdxqulOQw/RNe3lLVSUgeoFnrZPX1HWC/A1jOAQ67LY7yr/oj20jBUVsYmvKVk2VAkt8bg4wzgBvadSOw2/CRxvi72iUeLhIfdNh5lTgfS8BYlureod8tX0kWkDevqFaGdwKWEVBLQBs3RpdYqtWs1pKAfKSUm+Da8AtEYVOboGFnXDWb5DKMO2V5h0aciuPdjU5dl93ybLvnUgAAAABJRU5ErkJggg==',
65+
rest: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACqSURBVHgB3ZTbDcMgDEUvVQboCOkEzQh0g46QUTJKN2i7STeADZoNXKP6Kw/skPwkR7KEBD5cAQLYLUQUKE+aP1tlnmzUw97TjLOBzsM5F2GBd36WpMsJgyJ7LZHVejjyc/1TZ6idX88VZePRLVcTDR55kiTIOHJdoCS8ws5bXUF2Olig/6Nupb6rZMa0HUrgxmYzmQjvA1mLNaQ0JbIqM9dL3fgT+OAw/AAIe3FCMXAQxAAAAABJRU5ErkJggg==',
66+
'rest-exceed':
67+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADoSURBVHgB3ZIxDoIwFIb/Eg7gEWBxldFNiLOJR+AG6g3AY+Cio6NxcDK66cgN8AawmLjVV62JkhYrOBi/5IWmr+/j5bXAr8N0Cc55Rh9HmdytgeMWuJwLMGvO4mRSKSSZL8qUss3yLnvtK2bTJBIrC2o86EgPik0+eqx0wh5q8nmH3b5qd6EV0vwc6C5DEAwAt72n6Re3AKf5zcba8yQc8mpy8VMZrXK9rXD6qEZIMrk+UbjPSdUMOzBn9fYENyeCCeJRU4Qy8kYyw24j1IEKva/JpLD8fEI0QXRTR2ZX5AoZAWMsxd9wBR1E9hNu08zLAAAAAElFTkSuQmCC',
6268
};
6369
const getTrayIcon = (icon: string): NativeImage => {
64-
return nativeImage.createFromDataURL(trayIconMap[icon] ?? trayIconMap['cat']);
70+
return nativeImage.createFromDataURL(trayIconMap[icon] ?? trayIconMap.default);
6571
};
6672

6773
const createTray = (mainWindow: BrowserWindow) => {
@@ -127,6 +133,9 @@ app.whenReady().then(() => {
127133
ipcMain.handle('change-tray-icon', (event, icon: string) => {
128134
tray?.setImage(getTrayIcon(icon));
129135
});
136+
ipcMain.handle('change-tray-title', (event, title: string) => {
137+
tray?.setTitle(title);
138+
});
130139
ipcMain.handle('get-always-on-top', () => {
131140
return mainWindow?.isAlwaysOnTop();
132141
});

src/preload/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const electronAPI: IElectronAPI = {
88
showWindow: () => ipcRenderer.send('show-window'),
99
getMachineId: () => ipcRenderer.invoke('get-machine-id'),
1010
changeTrayIcon: (icon: string) => ipcRenderer.invoke('change-tray-icon', icon),
11+
changeTrayTitle: (title: string) => ipcRenderer.invoke('change-tray-title', title),
1112
getAlwaysOnTop: () => ipcRenderer.invoke('get-always-on-top'),
1213
setAlwaysOnTop: (isAlwaysOnTop: boolean) =>
1314
ipcRenderer.invoke('set-always-on-top', isAlwaysOnTop),

src/renderer/features/pomodoro/hooks/use-pomodoro.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { useEffect } from 'react';
2+
13
import { useLocalStorage } from 'usehooks-ts';
24

35
import { PomodoroCycle, PomodoroEndReason, PomodoroMode, PomodoroTime } from '@/entities/pomodoro';
46
import { LOCAL_STORAGE_KEY } from '@/shared/constants';
57
import { useInterval } from '@/shared/hooks';
8+
import { msToTime, padNumber } from '@/shared/utils';
69

710
// == usePomodoro 로직에 대한 description
811

@@ -64,6 +67,23 @@ const updateCycles = (cycles: PomodoroCycle[], nextCycle?: PomodoroCycle): Pomod
6467
].filter(isNotNil);
6568
};
6669

70+
const getFormattedTime = (goalTime: number, { elapsed, exceeded }: PomodoroTime) => {
71+
const isExceed = exceeded > 0;
72+
const { minutes, seconds } = msToTime(isExceed ? exceeded : goalTime - elapsed);
73+
const time = `${padNumber(minutes)}:${padNumber(seconds)}`;
74+
return { isExceed, time };
75+
};
76+
77+
const getTrayIcon = (mode: PomodoroMode, isExceed: boolean) => {
78+
if (mode === 'focus') {
79+
return isExceed ? 'focus-exceed' : 'focus';
80+
}
81+
if (mode === 'rest') {
82+
return isExceed ? 'rest-exceed' : 'rest';
83+
}
84+
return '';
85+
};
86+
6787
export const getPomodoroTime = (cycle: PomodoroCycle): PomodoroTime => {
6888
const now = cycle.endAt ?? Date.now();
6989
const maxElapsedTime = cycle.goalTime + cycle.exceedMaxTime;
@@ -171,6 +191,24 @@ export const usePomodoro = ({
171191
pomodoroCycles.length > 0 ? 250 : null,
172192
);
173193

194+
useEffect(() => {
195+
const currentCycle = pomodoroCycles[pomodoroCycles.length - 1];
196+
if (!currentCycle) {
197+
window.electronAPI.changeTrayIcon('');
198+
window.electronAPI.changeTrayTitle('');
199+
return;
200+
}
201+
202+
const { isExceed, time: trayTitle } = getFormattedTime(currentCycle.goalTime, pomodoroTime);
203+
const mode = currentCycle.mode;
204+
// 휴식 대기 중에는 직전의 집중 시간을 표시하기 위함
205+
if (mode !== 'rest-wait') {
206+
const trayIcon = getTrayIcon(mode, isExceed);
207+
window.electronAPI.changeTrayIcon(trayIcon);
208+
window.electronAPI.changeTrayTitle(trayTitle);
209+
}
210+
}, [pomodoroCycles, pomodoroTime]);
211+
174212
return {
175213
pomodoroCycles,
176214
pomodoroTime,

src/shared/type.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// @see: https://www.electronjs.org/docs/latest/tutorial/context-isolation#usage-with-typescript
22
export interface IElectronAPI {
33
showWindow: () => void;
4-
changeTrayIcon: (icon: string) => void;
54
getMachineId: () => Promise<string>;
5+
changeTrayIcon: (icon: string) => void;
6+
changeTrayTitle: (title: string) => void;
67
getAlwaysOnTop: () => Promise<boolean>;
78
setAlwaysOnTop: (isAlwaysOnTop: boolean) => Promise<void>;
89
getMinimized: () => Promise<boolean>;

0 commit comments

Comments
 (0)