From 939f72bef69c7f7b92731152770afece59d57edc Mon Sep 17 00:00:00 2001 From: Young-do Cho Date: Sun, 24 Nov 2024 17:53:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=81=EB=8B=A8=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=20&=20=ED=99=94=EB=A9=B4=20=EC=B6=95=EC=86=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pin, minimize 버튼 추가 * 집중, 휴식 최소화 화면 ui만 추가 * 최소화, 고정모드 기능 추가 * 이미지 영역 털뭉치 이미지로 적용 * 타이틀바 숨기고 drag, nodrag 영역 설정 * 축소 ui에도 drag 영역 설정 * pin off시 한번 focus 되도록 Co-authored-by: halang --------- Co-authored-by: halang --- src/main/main.ts | 25 +++ src/preload/preload.ts | 5 + src/renderer/app/index.css | 10 + src/renderer/pages/pomodoro.tsx | 118 +++++----- .../shared/assets/images/hairball.png | Bin 0 -> 12290 bytes .../shared/assets/svgs/minimize-off.svg | 3 + .../shared/assets/svgs/minimize-on.svg | 3 + src/renderer/shared/assets/svgs/pin-off.svg | 3 + src/renderer/shared/assets/svgs/pin-on.svg | 3 + src/renderer/shared/hooks/index.ts | 2 + .../shared/hooks/use-always-on-top.ts | 19 ++ src/renderer/shared/hooks/use-minimize.ts | 19 ++ src/renderer/shared/ui/dialog.tsx | 2 +- src/renderer/shared/ui/drawer.tsx | 2 +- src/renderer/shared/ui/icon.tsx | 8 + src/renderer/shared/ui/layouts.tsx | 13 +- .../widgets/pomodoro/ui/focus-screen.tsx | 169 ++++++++++---- .../widgets/pomodoro/ui/home-screen.tsx | 6 +- .../widgets/pomodoro/ui/rest-screen.tsx | 209 ++++++++++++------ .../widgets/pomodoro/ui/rest-wait-screen.tsx | 138 ++++++------ src/shared/type.ts | 4 + 21 files changed, 519 insertions(+), 242 deletions(-) create mode 100644 src/renderer/shared/assets/images/hairball.png create mode 100644 src/renderer/shared/assets/svgs/minimize-off.svg create mode 100644 src/renderer/shared/assets/svgs/minimize-on.svg create mode 100644 src/renderer/shared/assets/svgs/pin-off.svg create mode 100644 src/renderer/shared/assets/svgs/pin-on.svg create mode 100644 src/renderer/shared/hooks/use-always-on-top.ts create mode 100644 src/renderer/shared/hooks/use-minimize.ts diff --git a/src/main/main.ts b/src/main/main.ts index 96da219..0518753 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -29,6 +29,7 @@ const createWindow = () => { height: 800, minHeight: 800, title: '', + titleBarStyle: 'hidden', }); // and load the index.html of the app. @@ -126,4 +127,28 @@ app.whenReady().then(() => { ipcMain.handle('change-tray-icon', (event, icon: string) => { tray?.setImage(getTrayIcon(icon)); }); + ipcMain.handle('get-always-on-top', () => { + return mainWindow?.isAlwaysOnTop(); + }); + ipcMain.handle('set-always-on-top', (event, isAlwaysOnTop: boolean) => { + if (isAlwaysOnTop) { + mainWindow?.setAlwaysOnTop(true, 'screen-saver'); + } else { + mainWindow?.focus(); + mainWindow?.setAlwaysOnTop(false); + } + }); + ipcMain.handle('get-minimized', () => { + const [, height] = mainWindow?.getMinimumSize() || [0, 0]; + return height === 220; + }); + ipcMain.handle('set-minimized', (event, isMinimized: boolean) => { + if (isMinimized) { + mainWindow?.setMinimumSize(400, 220); + mainWindow?.setSize(400, 220); + } else { + mainWindow?.setMinimumSize(400, 800); + mainWindow?.setSize(400, 800); + } + }); }); diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 48ae724..8fead74 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -8,6 +8,11 @@ const electronAPI: IElectronAPI = { showWindow: () => ipcRenderer.send('show-window'), getMachineId: () => ipcRenderer.invoke('get-machine-id'), changeTrayIcon: (icon: string) => ipcRenderer.invoke('change-tray-icon', icon), + getAlwaysOnTop: () => ipcRenderer.invoke('get-always-on-top'), + setAlwaysOnTop: (isAlwaysOnTop: boolean) => + ipcRenderer.invoke('set-always-on-top', isAlwaysOnTop), + getMinimized: () => ipcRenderer.invoke('get-minimized'), + setMinimized: (isMinimized: boolean) => ipcRenderer.invoke('set-minimized', isMinimized), }; contextBridge.exposeInMainWorld('electronAPI', electronAPI); diff --git a/src/renderer/app/index.css b/src/renderer/app/index.css index ad1a825..ba0e385 100644 --- a/src/renderer/app/index.css +++ b/src/renderer/app/index.css @@ -100,3 +100,13 @@ @apply text-[12px] font-normal leading-[16px] tracking-[-.01em]; } } + +.drag-region { + -webkit-app-region: drag; +} +.drag-region > *:not(.drag-region) { + -webkit-app-region: no-drag; +} +.no-drag-region { + -webkit-app-region: no-drag; +} diff --git a/src/renderer/pages/pomodoro.tsx b/src/renderer/pages/pomodoro.tsx index 70ff98a..10b3162 100644 --- a/src/renderer/pages/pomodoro.tsx +++ b/src/renderer/pages/pomodoro.tsx @@ -8,8 +8,8 @@ import { TimeoutDialog } from '@/features/pomodoro/ui/timeout-dialog'; import { useFocusNotification } from '@/features/time'; import { useUser } from '@/features/user'; import { MINUTES_GAP } from '@/shared/constants'; -import { useDisclosure } from '@/shared/hooks'; -import { SidebarLayout, SimpleLayout, useToast } from '@/shared/ui'; +import { useAlwaysOnTop, useDisclosure, useMinimize } from '@/shared/hooks'; +import { useToast } from '@/shared/ui'; import { createIsoDuration, isoDurationToMs, @@ -54,6 +54,8 @@ const Pomodoro = () => { const { data: user } = useUser(); const { mutate: updateCategory } = useUpdateCategory(); const { mutate: savePomodoro } = useAddPomodoro(); + const { minimized, setMinimized } = useMinimize(); + const { alwaysOnTop, setAlwaysOnTop } = useAlwaysOnTop(); const [currentCategory, setCurrentCategory] = useState(categories?.[0]); const currentCategoryTitle = currentCategory?.title || ''; @@ -138,71 +140,77 @@ const Pomodoro = () => { setSelectedNextAction(undefined); }; + useEffect(() => { + // 휴식 대기중이거나 시작 대기전에는 최소화 및 항상 위에 표시를 해제 + if (mode !== 'focus' && mode !== 'rest') { + setMinimized(false); + setAlwaysOnTop(false); + } + }, [mode]); + if (mode === 'focus') return ( - - { - startRestWait(); - }} - handleEnd={() => { - endPomodoro(); - }} - /> - + ); if (mode === 'rest-wait') return ( - - { - updateCategoryTime('focusTime', currentFocusMinutes); - startRest(); - }} - handleEnd={() => { - updateCategoryTime('focusTime', currentFocusMinutes); - endPomodoro(); - }} - /> - + { + updateCategoryTime('focusTime', currentFocusMinutes); + startRest(); + }} + handleEnd={() => { + updateCategoryTime('focusTime', currentFocusMinutes); + endPomodoro(); + }} + /> ); if (mode === 'rest') return ( - - { - updateCategoryTime('restTime', currentRestMinutes); - startFocus(); - }} - handleEnd={() => { - updateCategoryTime('restTime', currentRestMinutes); - endPomodoro(); - }} - /> - + { + updateCategoryTime('restTime', currentRestMinutes); + startFocus(); + }} + handleEnd={() => { + updateCategoryTime('restTime', currentRestMinutes); + endPomodoro(); + }} + setMinimized={setMinimized} + setAlwaysOnTop={setAlwaysOnTop} + /> ); return ( - + <> { description={timeoutMessageMap[timeoutMode].description} /> )} - + ); }; diff --git a/src/renderer/shared/assets/images/hairball.png b/src/renderer/shared/assets/images/hairball.png new file mode 100644 index 0000000000000000000000000000000000000000..7855c8cdfc80ca8192dbbd4c330d2404d72f147e GIT binary patch literal 12290 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 Q%056*R!yc_$~^Rc05a5)>Hq)$ literal 0 HcmV?d00001 diff --git a/src/renderer/shared/assets/svgs/minimize-off.svg b/src/renderer/shared/assets/svgs/minimize-off.svg new file mode 100644 index 0000000..8ab8a0d --- /dev/null +++ b/src/renderer/shared/assets/svgs/minimize-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/shared/assets/svgs/minimize-on.svg b/src/renderer/shared/assets/svgs/minimize-on.svg new file mode 100644 index 0000000..bdea5ed --- /dev/null +++ b/src/renderer/shared/assets/svgs/minimize-on.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/shared/assets/svgs/pin-off.svg b/src/renderer/shared/assets/svgs/pin-off.svg new file mode 100644 index 0000000..0a638ca --- /dev/null +++ b/src/renderer/shared/assets/svgs/pin-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/shared/assets/svgs/pin-on.svg b/src/renderer/shared/assets/svgs/pin-on.svg new file mode 100644 index 0000000..98c077f --- /dev/null +++ b/src/renderer/shared/assets/svgs/pin-on.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/renderer/shared/hooks/index.ts b/src/renderer/shared/hooks/index.ts index e492e3e..57780a0 100644 --- a/src/renderer/shared/hooks/index.ts +++ b/src/renderer/shared/hooks/index.ts @@ -7,3 +7,5 @@ export * from './use-disclosure'; export * from './use-notification'; export * from './use-rive-cat'; export * from './use-interval'; +export * from './use-minimize'; +export * from './use-always-on-top'; diff --git a/src/renderer/shared/hooks/use-always-on-top.ts b/src/renderer/shared/hooks/use-always-on-top.ts new file mode 100644 index 0000000..f956616 --- /dev/null +++ b/src/renderer/shared/hooks/use-always-on-top.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + +export const useAlwaysOnTop = () => { + const [alwaysOnTop, setAlwaysOnTop] = useState(false); + + useEffect(() => { + const run = async () => { + const realAlwaysOnTop = await window.electronAPI.getAlwaysOnTop(); + if (realAlwaysOnTop !== alwaysOnTop) { + await window.electronAPI.setAlwaysOnTop(alwaysOnTop); + setAlwaysOnTop(alwaysOnTop); + } + }; + + run(); + }, [alwaysOnTop]); + + return { alwaysOnTop, setAlwaysOnTop }; +}; diff --git a/src/renderer/shared/hooks/use-minimize.ts b/src/renderer/shared/hooks/use-minimize.ts new file mode 100644 index 0000000..d97b2c5 --- /dev/null +++ b/src/renderer/shared/hooks/use-minimize.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + +export const useMinimize = () => { + const [minimized, setMinimized] = useState(false); + + useEffect(() => { + const run = async () => { + const realMinimized = await window.electronAPI.getMinimized(); + if (realMinimized !== minimized) { + await window.electronAPI.setMinimized(minimized); + setMinimized(minimized); + } + }; + + run(); + }, [minimized]); + + return { minimized, setMinimized }; +}; diff --git a/src/renderer/shared/ui/dialog.tsx b/src/renderer/shared/ui/dialog.tsx index f06e1f9..dcd3a2e 100644 --- a/src/renderer/shared/ui/dialog.tsx +++ b/src/renderer/shared/ui/dialog.tsx @@ -30,7 +30,7 @@ export const Dialog = ({ (({ className, ...props }, ref) => ( )); diff --git a/src/renderer/shared/ui/icon.tsx b/src/renderer/shared/ui/icon.tsx index ab78031..50363ad 100644 --- a/src/renderer/shared/ui/icon.tsx +++ b/src/renderer/shared/ui/icon.tsx @@ -13,9 +13,13 @@ import ClockIcon from '@/shared/assets/svgs/clock.svg'; import CloseIcon from '@/shared/assets/svgs/close.svg'; import FocusTimeIcon from '@/shared/assets/svgs/focus-time.svg'; import MenuIcon from '@/shared/assets/svgs/menu.svg?react'; +import MinimizeOff from '@/shared/assets/svgs/minimize-off.svg'; +import MinimizeOn from '@/shared/assets/svgs/minimize-on.svg'; import MinusIcon from '@/shared/assets/svgs/minus.svg'; import MinusSvgIcon from '@/shared/assets/svgs/minus.svg?react'; import PenIcon from '@/shared/assets/svgs/pen.svg'; +import PinOff from '@/shared/assets/svgs/pin-off.svg'; +import PinOn from '@/shared/assets/svgs/pin-on.svg'; import PlaceholderIcon from '@/shared/assets/svgs/placeholder.svg'; import PlayIcon from '@/shared/assets/svgs/play.svg'; import PlusIcon from '@/shared/assets/svgs/plus.svg'; @@ -50,6 +54,10 @@ const icons = { clock: ClockIcon, readyForStat: ReadyForStatIcon, clockLine: ClockLineIcon, + pinOff: PinOff, + pinOn: PinOn, + minimizeOff: MinimizeOff, + minimizeOn: MinimizeOn, } as const; const sizes = { xs: 16, diff --git a/src/renderer/shared/ui/layouts.tsx b/src/renderer/shared/ui/layouts.tsx index fb351a0..ef484d6 100644 --- a/src/renderer/shared/ui/layouts.tsx +++ b/src/renderer/shared/ui/layouts.tsx @@ -10,7 +10,12 @@ export type SimpleLayoutProps = { }; export const SimpleLayout = ({ children }: SimpleLayoutProps) => { - return
{children}
; + return ( +
+
+
{children}
+
+ ); }; export type SidebarLayoutProps = { @@ -21,11 +26,11 @@ export type SidebarLayoutProps = { export const SidebarLayout = ({ title, children }: SidebarLayoutProps) => { return (
-
+
-
-

{title}

+
+

{title}

{children} diff --git a/src/renderer/widgets/pomodoro/ui/focus-screen.tsx b/src/renderer/widgets/pomodoro/ui/focus-screen.tsx index 5556bd0..aad7608 100644 --- a/src/renderer/widgets/pomodoro/ui/focus-screen.tsx +++ b/src/renderer/widgets/pomodoro/ui/focus-screen.tsx @@ -1,8 +1,9 @@ import { Time } from '@/features/time'; import { useUser } from '@/features/user'; +import hairballImage from '@/shared/assets/images/hairball.png'; import catFocusMotionRiveFile from '@/shared/assets/rivs/cat_focus.riv'; import { useRiveCat } from '@/shared/hooks'; -import { Button, Icon, Tooltip } from '@/shared/ui'; +import { Button, Icon, SimpleLayout, Tooltip } from '@/shared/ui'; import { cn, getCategoryIconName, msToTime } from '@/shared/utils'; type FocusScreenProps = { @@ -10,8 +11,12 @@ type FocusScreenProps = { currentFocusTime: number; elapsedTime: number; exceededTime: number; + minimized: boolean; + alwaysOnTop: boolean; handleRest: () => void; handleEnd: () => void; + setMinimized: (next: boolean) => void; + setAlwaysOnTop: (next: boolean) => void; }; const toolTipContentMap: Record = { @@ -24,8 +29,12 @@ export const FocusScreen = ({ currentFocusTime, elapsedTime, exceededTime, + minimized, + alwaysOnTop, handleRest, handleEnd, + setMinimized, + setAlwaysOnTop, }: FocusScreenProps) => { const isExceed = exceededTime > 0; const { minutes, seconds } = msToTime(currentFocusTime - elapsedTime); @@ -38,57 +47,125 @@ export const FocusScreen = ({ userCatType: user?.cat?.type, }); - return ( -
-
-
- - {currentCategory} + if (minimized) { + return ( +
+
+
+
+ + +
+
+
+
+

+ + {currentCategory} +

+
+
+
-
-
- - { - clickCatInput?.fire(); - }} - /> -
-
- - 집중시간 +
+ ); + } + + return ( + +
+
+
+ + {currentCategory} +
+
+
+ +
-
+
+ + { + clickCatInput?.fire(); + }} + /> +
+
+ + 집중시간 +
+
+
+ +
-
-
- -
-
+ ); }; diff --git a/src/renderer/widgets/pomodoro/ui/home-screen.tsx b/src/renderer/widgets/pomodoro/ui/home-screen.tsx index 92b14ab..5fd2a98 100644 --- a/src/renderer/widgets/pomodoro/ui/home-screen.tsx +++ b/src/renderer/widgets/pomodoro/ui/home-screen.tsx @@ -9,7 +9,7 @@ import { useUser } from '@/features/user'; import catHomeMotionRiveFile from '@/shared/assets/rivs/cat_home.riv'; import { LOCAL_STORAGE_KEY } from '@/shared/constants'; import { useDisclosure, useRiveCat } from '@/shared/hooks'; -import { Button, Guide, Icon, Tooltip, useToast } from '@/shared/ui'; +import { Button, Guide, Icon, SidebarLayout, Tooltip, useToast } from '@/shared/ui'; import { getCategoryIconName, createIsoDuration } from '@/shared/utils'; const steps = [ @@ -72,7 +72,7 @@ export const HomeScreen = ({ }; return ( - <> +
- + ); }; diff --git a/src/renderer/widgets/pomodoro/ui/rest-screen.tsx b/src/renderer/widgets/pomodoro/ui/rest-screen.tsx index be6ffa6..d7a8384 100644 --- a/src/renderer/widgets/pomodoro/ui/rest-screen.tsx +++ b/src/renderer/widgets/pomodoro/ui/rest-screen.tsx @@ -1,10 +1,11 @@ import { PomodoroNextAction } from '@/entities/pomodoro'; import { Time } from '@/features/time'; import { useUser } from '@/features/user'; +import hairballImage from '@/shared/assets/images/hairball.png'; import catRestMotionRiveFile from '@/shared/assets/rivs/cat_rest.riv'; import { MAX_REST_MINUTES, MIN_REST_MINUTES, MINUTES_GAP } from '@/shared/constants'; import { useRiveCat } from '@/shared/hooks'; -import { Button, Icon, SelectGroup, SelectGroupItem, Tooltip } from '@/shared/ui'; +import { Button, Icon, SelectGroup, SelectGroupItem, SimpleLayout, Tooltip } from '@/shared/ui'; import { cn, getCategoryIconName, msToTime } from '@/shared/utils'; type RestScreenProps = { @@ -14,9 +15,13 @@ type RestScreenProps = { exceededTime: number; currentRestMinutes: number; selectedNextAction: PomodoroNextAction | undefined; + minimized: boolean; + alwaysOnTop: boolean; setSelectedNextAction: (nextAction: PomodoroNextAction) => void; handleFocus: () => void; handleEnd: () => void; + setMinimized: (next: boolean) => void; + setAlwaysOnTop: (next: boolean) => void; }; export const RestScreen = ({ @@ -26,9 +31,13 @@ export const RestScreen = ({ exceededTime, currentRestMinutes, selectedNextAction, + minimized, + alwaysOnTop, setSelectedNextAction, handleFocus, handleEnd, + setMinimized, + setAlwaysOnTop, }: RestScreenProps) => { const isExceed = exceededTime > 0; const { minutes, seconds } = msToTime(currentRestTime - elapsedTime); @@ -41,80 +50,148 @@ export const RestScreen = ({ userCatType: user?.cat?.type, }); - return ( -
-
-
- - {currentCategory} -
-
- -
- - { - clickCatInput?.fire(); - }} - /> -
-
- - 휴식시간 + if (minimized) { + return ( +
+
+
+
+ +
-
+
+
+

+ + {currentCategory} +

+
+
+
+ ); + } -
-

다음부터 휴식시간을 바꿀까요?

- - +
+
+
+ + {currentCategory} +
+
+
+ +
-
+ + +
+ + +
+ + { + clickCatInput?.fire(); + }} + /> +
+
+ + 휴식시간 +
+
-
- - +
+

다음부터 휴식시간을 바꿀까요?

+ + + + 5분 + + = MAX_REST_MINUTES} + value="plus" + className="flex flex-row items-center justify-center gap-1 px-3 py-2" + > + + 5분 + + +
+
+ +
+ + +
-
+ ); }; diff --git a/src/renderer/widgets/pomodoro/ui/rest-wait-screen.tsx b/src/renderer/widgets/pomodoro/ui/rest-wait-screen.tsx index 7689747..0a8dc55 100644 --- a/src/renderer/widgets/pomodoro/ui/rest-wait-screen.tsx +++ b/src/renderer/widgets/pomodoro/ui/rest-wait-screen.tsx @@ -6,7 +6,7 @@ import { Time } from '@/features/time'; import completeFocusLottie from '@/shared/assets/lotties/loti_complete_focus.json?url'; import particleLottie from '@/shared/assets/lotties/loti_particle.json?url'; import { MAX_FOCUS_MINUTES, MIN_FOCUS_MINUTES, MINUTES_GAP } from '@/shared/constants'; -import { Button, Icon, SelectGroup, SelectGroupItem } from '@/shared/ui'; +import { Button, Icon, SelectGroup, SelectGroupItem, SimpleLayout } from '@/shared/ui'; import { msToTime } from '@/shared/utils'; type RestWaitScreenProps = { @@ -36,38 +36,31 @@ export const RestWaitScreen = ({ const { minutes: exceedMinutes, seconds: exceedSeconds } = msToTime(exceededTime); return ( -
-
-
-
- -
-
- - {isExceed && ( + +
+
+
+
+ +
+
- )} -
-
-

다음부터 집중시간을 바꿀까요?

- - - - 5분 - - = MAX_FOCUS_MINUTES} - value="plus" - className="flex flex-row items-center justify-center gap-1 px-3 py-2" + {isExceed && ( + + )} +
+
+

다음부터 집중시간을 바꿀까요?

+ - - 5분 - - + + + 5분 + + = MAX_FOCUS_MINUTES} + value="plus" + className="flex flex-row items-center justify-center gap-1 px-3 py-2" + > + + 5분 + + +
+
+
+ +
-
-
- -
-
+ ); }; diff --git a/src/shared/type.ts b/src/shared/type.ts index fd1d017..a326623 100644 --- a/src/shared/type.ts +++ b/src/shared/type.ts @@ -3,4 +3,8 @@ export interface IElectronAPI { showWindow: () => void; changeTrayIcon: (icon: string) => void; getMachineId: () => Promise; + getAlwaysOnTop: () => Promise; + setAlwaysOnTop: (isAlwaysOnTop: boolean) => Promise; + getMinimized: () => Promise; + setMinimized: (isMinimized: boolean) => Promise; }