Close book in reader header bar

This commit is contained in:
chrox
2024-10-29 12:13:29 +01:00
parent 32d12287df
commit f341bd4b5e
5 changed files with 171 additions and 78 deletions
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import { FaSearch } from 'react-icons/fa';
import { PiPlus } from 'react-icons/pi';
import { PiSelectionAllDuotone } from 'react-icons/pi';
@@ -16,6 +16,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
onImportBooks,
onToggleSelectMode,
}) => {
const headerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey || e.shiftKey) {
@@ -28,9 +29,24 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
};
}, [onToggleSelectMode]);
const handleMinimize = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().minimize();
};
const handleToggleMaximize = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().toggleMaximize();
};
const handleClose = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().close();
};
return (
<div
id='titlebar'
ref={headerRef}
className='titlebar rounded-window fixed top-0 z-10 w-full bg-gray-100 px-6 py-4'
>
<div className='flex items-center justify-between'>
@@ -68,7 +84,12 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
</button>
</div>
</div>
<WindowButtons />
<WindowButtons
headerRef={headerRef}
onMinimize={handleMinimize}
onToggleMaximize={handleToggleMaximize}
onClose={handleClose}
/>
</div>
</div>
);
+1 -1
View File
@@ -7,7 +7,7 @@ import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import Spinner from '@/components/Spinner';
import LibraryHeader from '@/app/library/components/Header';
import LibraryHeader from '@/app/library/components/LibraryHeader';
import Bookshelf from '@/app/library/components/Bookshelf';
const LibraryPage = () => {
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import clsx from 'clsx';
import { VscLayoutSidebarLeft, VscLayoutSidebarLeftOff } from 'react-icons/vsc';
@@ -14,6 +14,7 @@ interface HeaderBarProps {
setSideBarVisibility: (visibility: boolean) => void;
setSideBarBookKey: (key: string) => void;
setHoveredBookKey: (key: string) => void;
onCloseBook: (bookKey: string) => void;
}
const HeaderBar: React.FC<HeaderBarProps> = ({
@@ -26,7 +27,9 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
setSideBarVisibility,
setSideBarBookKey,
setHoveredBookKey,
onCloseBook,
}) => {
const headerRef = useRef<HTMLDivElement>(null);
const toggleSideBar = () => {
if (!isSideBarVisible) {
setSideBarVisibility(true);
@@ -38,7 +41,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
return (
<div
id='titlebar'
ref={headerRef}
className={clsx(
`header-bar absolute top-0 z-10 flex h-11 w-full items-center px-4`,
`shadow-xs bg-base-100 rounded-window transition-opacity duration-300`,
@@ -48,13 +51,8 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<div className='absolute inset-0 flex items-center justify-center'>
<h2 className='line-clamp-1 max-w-[70%] px-2 text-center text-xs font-semibold'>
{bookTitle}
</h2>
</div>
<div className='absolute left-4 flex h-full items-center p-2'>
<button onClick={toggleSideBar}>
<div className='sidebar-toggler mr-auto flex h-full items-center'>
<button onClick={toggleSideBar} className='p-2'>
{sideBarBookKey === bookKey && isSideBarVisible ? (
<VscLayoutSidebarLeft size={16} />
) : (
@@ -62,8 +60,19 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
)}
</button>
</div>
<div className='absolute right-4 flex h-full items-center'>
<WindowButtons />
<div className='header-title flex flex-1 items-center justify-center'>
<h2 className='line-clamp-1 max-w-[80%] text-center text-xs font-semibold'>{bookTitle}</h2>
</div>
<div className='ml-auto flex h-full items-center space-x-2'>
<WindowButtons
className='window-buttons flex h-full items-center'
headerRef={headerRef}
showMinimize={false}
showMaximize={false}
onClose={() => onCloseBook(bookKey)}
/>
</div>
</div>
);
@@ -43,7 +43,7 @@ const ReaderContent = () => {
(e.altKey && e.key === 'Tab') ||
(e.metaKey && e.key === 'Tab')
) {
setSideBarBookKey((prevKey) => {
setSideBarBookKey((prevKey: string) => {
const index = ids.findIndex((id) => prevKey.startsWith(id));
const nextIndex = (index + 1) % ids.length;
return getKey(ids[nextIndex]!, nextIndex);
@@ -97,17 +97,43 @@ const ReaderContent = () => {
settings.globalReadSettings.isSideBarPinned = !isSideBarPinned;
};
const handleCloseBook = () => {
bookStates.forEach((bookState) => {
const { book, config, isPrimary } = bookState;
if (isPrimary && book && config) {
saveConfig(envConfig, book, config);
}
const closeBook = (bookKey: string) => {
const bookState = books[bookKey];
if (!bookState) return;
const { book, config, isPrimary } = bookState;
if (isPrimary && book && config) {
saveConfig(envConfig, book, config);
}
};
const handleCloseBooks = () => {
ids.forEach((id, index) => {
const key = getKey(id, index);
closeBook(key);
});
saveSettings(envConfig, settings);
router.back();
};
const handleCloseBook = (bookKey: string) => {
closeBook(bookKey);
setSideBarBookKey((prevKey: string) => {
if (prevKey === bookKey) {
const index = ids.findIndex((id) => prevKey.startsWith(id));
const nextIndex = (index + 1) % ids.length;
return getKey(ids[nextIndex]!, nextIndex);
}
return prevKey;
});
const newIds = ids.filter((id, index) => getKey(id, index) !== bookKey);
if (newIds.length > 0) {
router.replace(`/reader?ids=${newIds.join(',')}`);
} else {
saveSettings(envConfig, settings);
router.back();
}
};
const getGridTemplate = () => {
const count = ids.length;
const aspectRatio = window.innerWidth / window.innerHeight;
@@ -157,7 +183,7 @@ const ReaderContent = () => {
isPinned={isSideBarPinned}
onResize={handleSideBarResize}
onTogglePin={handleSideBarTogglePin}
onGoToLibrary={handleCloseBook}
onGoToLibrary={handleCloseBooks}
onSetVisibility={(visibility: boolean) => setSideBarVisibility(visibility)}
/>
@@ -186,6 +212,7 @@ const ReaderContent = () => {
setSideBarVisibility={setSideBarVisibility}
setSideBarBookKey={setSideBarBookKey}
setHoveredBookKey={setHoveredBookKey}
onCloseBook={handleCloseBook}
/>
<FoliateViewer
bookKey={key}
@@ -1,77 +1,113 @@
import React, { useEffect } from 'react';
import clsx from 'clsx';
import React, { useEffect, useRef } from 'react';
const WindowButtons: React.FC = () => {
useEffect(() => {
const handleMouseDown = async (e: MouseEvent) => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize();
} else {
getCurrentWindow().startDragging();
}
interface WindowButtonsProps {
className?: string;
headerRef: React.RefObject<HTMLDivElement>;
showMinimize?: boolean;
showMaximize?: boolean;
showClose?: boolean;
onMinimize?: () => void;
onToggleMaximize?: () => void;
onClose?: () => void;
}
const WindowButtons: React.FC<WindowButtonsProps> = ({
className,
headerRef,
showMinimize = true,
showMaximize = true,
showClose = true,
onMinimize,
onToggleMaximize,
onClose,
}) => {
const parentRef = useRef<HTMLDivElement>(null);
const handleMouseDown = async (e: MouseEvent) => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize();
} else {
getCurrentWindow().startDragging();
}
};
}
};
useEffect(() => {
const headerElement = headerRef.current;
headerElement?.addEventListener('mousedown', handleMouseDown);
const header = document.getElementById('titlebar');
header?.addEventListener('mousedown', handleMouseDown);
return () => {
header?.removeEventListener('mousedown', handleMouseDown);
headerElement?.removeEventListener('mousedown', handleMouseDown);
};
}, []);
const handleMinimize = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().minimize();
if (onMinimize) {
onMinimize();
}
};
const handleMaximize = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().toggleMaximize();
if (onToggleMaximize) {
onToggleMaximize();
}
};
const handleClose = async () => {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
getCurrentWindow().close();
if (onClose) {
onClose();
}
};
return (
<div className='window-buttons ml-6 flex h-8 items-center justify-end space-x-2'>
<button
onClick={handleMinimize}
className='window-button'
aria-label='Minimize'
id='titlebar-minimize'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path fill='currentColor' d='M20 14H4v-2h16' />
</svg>
</button>
<div
ref={parentRef}
className={clsx('window-buttons ml-6 flex h-8 items-center justify-end space-x-2', className)}
>
{showMinimize && (
<button
onClick={handleMinimize}
className='window-button'
aria-label='Minimize'
id='titlebar-minimize'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path fill='currentColor' d='M20 14H4v-2h16' />
</svg>
</button>
)}
<button
onClick={handleMaximize}
className='window-button'
aria-label='Maximize/Restore'
id='titlebar-maximize'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path fill='currentColor' d='M4 4h16v16H4zm2 4v10h12V8z' />
</svg>
</button>
{showMaximize && (
<button
onClick={handleMaximize}
className='window-button'
aria-label='Maximize/Restore'
id='titlebar-maximize'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path fill='currentColor' d='M4 4h16v16H4zm2 4v10h12V8z' />
</svg>
</button>
)}
<button
onClick={handleClose}
className='window-button'
aria-label='Close'
id='titlebar-close'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z'
/>
</svg>
</button>
{showClose && (
<button
onClick={handleClose}
className='window-button'
aria-label='Close'
id='titlebar-close'
>
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z'
/>
</svg>
</button>
)}
</div>
);
};