Close book in reader header bar
This commit is contained in:
+24
-3
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user