Skip to content

Commit

Permalink
feat: 상단 고정 & 화면 축소 기능 추가 (#60)
Browse files Browse the repository at this point in the history
* pin, minimize 버튼 추가

* 집중, 휴식 최소화 화면 ui만 추가

* 최소화, 고정모드 기능 추가

* 이미지 영역 털뭉치 이미지로 적용

* 타이틀바 숨기고 drag, nodrag 영역 설정

* 축소 ui에도 drag 영역 설정

* pin off시 한번 focus 되도록

Co-authored-by: halang <[email protected]>

---------

Co-authored-by: halang <[email protected]>
  • Loading branch information
young-do and haryung-lee authored Nov 24, 2024
1 parent a4bf244 commit 939f72b
Show file tree
Hide file tree
Showing 21 changed files with 519 additions and 242 deletions.
25 changes: 25 additions & 0 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const createWindow = () => {
height: 800,
minHeight: 800,
title: '',
titleBarStyle: 'hidden',
});

// and load the index.html of the app.
Expand Down Expand Up @@ -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);
}
});
});
5 changes: 5 additions & 0 deletions src/preload/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
10 changes: 10 additions & 0 deletions src/renderer/app/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
118 changes: 63 additions & 55 deletions src/renderer/pages/pomodoro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 || '';
Expand Down Expand Up @@ -138,71 +140,77 @@ const Pomodoro = () => {
setSelectedNextAction(undefined);
};

useEffect(() => {
// 휴식 대기중이거나 시작 대기전에는 최소화 및 항상 위에 표시를 해제
if (mode !== 'focus' && mode !== 'rest') {
setMinimized(false);
setAlwaysOnTop(false);
}
}, [mode]);

if (mode === 'focus')
return (
<SimpleLayout>
<FocusScreen
currentFocusTime={currentFocusTime}
elapsedTime={Math.min(pomodoroTime.elapsed, currentFocusTime)}
exceededTime={pomodoroTime.exceeded}
currentCategory={currentCategoryTitle}
handleRest={() => {
startRestWait();
}}
handleEnd={() => {
endPomodoro();
}}
/>
</SimpleLayout>
<FocusScreen
currentFocusTime={currentFocusTime}
elapsedTime={Math.min(pomodoroTime.elapsed, currentFocusTime)}
exceededTime={pomodoroTime.exceeded}
currentCategory={currentCategoryTitle}
minimized={minimized}
alwaysOnTop={alwaysOnTop}
handleRest={startRestWait}
handleEnd={endPomodoro}
setMinimized={setMinimized}
setAlwaysOnTop={setAlwaysOnTop}
/>
);

if (mode === 'rest-wait')
return (
<SimpleLayout>
<RestWaitScreen
elapsedTime={Math.min(latestFocusTime?.elapsed ?? 0, currentFocusTime)}
exceededTime={latestFocusTime?.exceeded ?? 0}
currentCategory={currentCategoryTitle}
currentFocusMinutes={currentFocusMinutes}
selectedNextAction={selectedNextAction}
setSelectedNextAction={setSelectedNextAction}
handleRest={() => {
updateCategoryTime('focusTime', currentFocusMinutes);
startRest();
}}
handleEnd={() => {
updateCategoryTime('focusTime', currentFocusMinutes);
endPomodoro();
}}
/>
</SimpleLayout>
<RestWaitScreen
elapsedTime={Math.min(latestFocusTime?.elapsed ?? 0, currentFocusTime)}
exceededTime={latestFocusTime?.exceeded ?? 0}
currentCategory={currentCategoryTitle}
currentFocusMinutes={currentFocusMinutes}
selectedNextAction={selectedNextAction}
setSelectedNextAction={setSelectedNextAction}
handleRest={() => {
updateCategoryTime('focusTime', currentFocusMinutes);
startRest();
}}
handleEnd={() => {
updateCategoryTime('focusTime', currentFocusMinutes);
endPomodoro();
}}
/>
);

if (mode === 'rest')
return (
<SimpleLayout>
<RestScreen
currentRestTime={currentRestTime}
elapsedTime={Math.min(pomodoroTime.elapsed, currentRestTime)}
exceededTime={pomodoroTime.exceeded}
currentCategory={currentCategoryTitle}
currentRestMinutes={currentRestMinutes}
selectedNextAction={selectedNextAction}
setSelectedNextAction={setSelectedNextAction}
handleFocus={() => {
updateCategoryTime('restTime', currentRestMinutes);
startFocus();
}}
handleEnd={() => {
updateCategoryTime('restTime', currentRestMinutes);
endPomodoro();
}}
/>
</SimpleLayout>
<RestScreen
currentRestTime={currentRestTime}
elapsedTime={Math.min(pomodoroTime.elapsed, currentRestTime)}
exceededTime={pomodoroTime.exceeded}
currentCategory={currentCategoryTitle}
currentRestMinutes={currentRestMinutes}
selectedNextAction={selectedNextAction}
minimized={minimized}
alwaysOnTop={alwaysOnTop}
setSelectedNextAction={setSelectedNextAction}
handleFocus={() => {
updateCategoryTime('restTime', currentRestMinutes);
startFocus();
}}
handleEnd={() => {
updateCategoryTime('restTime', currentRestMinutes);
endPomodoro();
}}
setMinimized={setMinimized}
setAlwaysOnTop={setAlwaysOnTop}
/>
);

return (
<SidebarLayout>
<>
<HomeScreen
startTimer={startFocus}
currentCategory={currentCategoryTitle}
Expand All @@ -220,7 +228,7 @@ const Pomodoro = () => {
description={timeoutMessageMap[timeoutMode].description}
/>
)}
</SidebarLayout>
</>
);
};

Expand Down
Binary file added src/renderer/shared/assets/images/hairball.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/shared/assets/svgs/minimize-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/shared/assets/svgs/minimize-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/shared/assets/svgs/pin-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/shared/assets/svgs/pin-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/renderer/shared/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
19 changes: 19 additions & 0 deletions src/renderer/shared/hooks/use-always-on-top.ts
Original file line number Diff line number Diff line change
@@ -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 };
};
19 changes: 19 additions & 0 deletions src/renderer/shared/hooks/use-minimize.ts
Original file line number Diff line number Diff line change
@@ -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 };
};
2 changes: 1 addition & 1 deletion src/renderer/shared/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const Dialog = ({
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
className={cn(
'fixed inset-0 z-50',
'no-drag-region fixed inset-0 z-50',
!fullScreen && 'bg-black/80',
animated &&
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/shared/ui/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const DrawerOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn('fixed inset-0 z-50 bg-black/80', className)}
className={cn('no-drag-region fixed inset-0 z-50 bg-black/80', className)}
{...props}
/>
));
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/shared/ui/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 9 additions & 4 deletions src/renderer/shared/ui/layouts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export type SimpleLayoutProps = {
};

export const SimpleLayout = ({ children }: SimpleLayoutProps) => {
return <div className="m-auto h-dvh max-w-md bg-background-primary">{children}</div>;
return (
<div className="relative m-auto h-dvh max-w-md bg-background-primary">
<div className="drag-region fixed left-0 right-0 top-0 h-[28px]" />
<div className="absolute bottom-0 left-0 right-0 top-[28px]">{children}</div>
</div>
);
};

export type SidebarLayoutProps = {
Expand All @@ -21,11 +26,11 @@ export type SidebarLayoutProps = {
export const SidebarLayout = ({ title, children }: SidebarLayoutProps) => {
return (
<div className="relative h-dvh w-full">
<div className="absolute bottom-0 left-0 top-0 w-[68px] bg-background-secondary pt-[52px]">
<div className="drag-region absolute bottom-0 left-0 top-0 w-[68px] bg-background-secondary pt-[52px]">
<DesktopSidebar />
</div>
<div className="absolute left-[68px] right-0 top-0 h-[52px] bg-background-primary">
<h1 className="body-sb flex h-full items-center pl-3 text-text-primary">{title}</h1>
<div className="drag-region absolute left-[68px] right-0 top-0 h-[52px] bg-background-primary">
<h1 className="body-sb inline-flex h-full items-center pl-3 text-text-primary">{title}</h1>
</div>
<div className="absolute bottom-0 left-[68px] right-0 top-[52px] bg-background-primary">
{children}
Expand Down
Loading

0 comments on commit 939f72b

Please sign in to comment.