Refactoring ReaderContent into FooterBar and HeaderBar

This commit is contained in:
chrox
2024-10-25 14:29:40 +02:00
parent 4e6b13960b
commit 4a36c81be2
8 changed files with 436 additions and 496 deletions
+3 -2
View File
@@ -23,6 +23,7 @@
"@tauri-apps/plugin-log": "^2.0.0",
"@tauri-apps/plugin-os": "^2.0.0",
"@zip.js/zip.js": "^2.7.52",
"clsx": "^2.1.1",
"epubjs": "^0.3.93",
"foliate-js": "workspace:*",
"js-md5": "^0.8.3",
@@ -42,8 +43,8 @@
"cross-var": "^1.1.0",
"daisyui": "^4.12.10",
"dotenv-cli": "^7.4.2",
"eslint": "^8",
"eslint-config-next": "14.2.13",
"eslint": "^9",
"eslint-config-next": "^15",
"mkdirp": "^3.0.1",
"postcss": "^8",
"tailwindcss": "^3.4.1",
+2 -5
View File
@@ -33,10 +33,7 @@ foliate-view {
border: none;
}
.foliate-viewer {
.hover-bar-anim:hover ~ .foliate-viewer {
transform: scale(1.02);
transition: transform 0.3s ease;
}
.header-bar-anim:hover + .foliate-viewer {
transform: scale(1.02);
}
@@ -0,0 +1,72 @@
import React from 'react';
import clsx from 'clsx';
import { RiArrowLeftWideLine, RiArrowRightWideLine } from 'react-icons/ri';
import { useReaderStore } from '@/store/readerStore';
interface FooterBarProps {
bookKey: string;
progress: number | undefined;
isHoveredAnim: boolean;
hoveredBookKey: string;
setHoveredBookKey: (key: string) => void;
}
const FooterBar: React.FC<FooterBarProps> = ({
bookKey,
progress,
isHoveredAnim,
hoveredBookKey,
setHoveredBookKey,
}) => {
const { getFoliateView } = useReaderStore();
const handleProgressChange = (event: React.ChangeEvent) => {
const newProgress = parseInt((event.target as HTMLInputElement).value, 10);
const foliateView = getFoliateView(bookKey);
foliateView?.goToFraction(newProgress / 100.0);
};
const handleGoPrev = () => {
const foliateView = getFoliateView(bookKey);
foliateView?.goLeft();
};
const handleGoNext = () => {
const foliateView = getFoliateView(bookKey);
foliateView?.goRight();
};
return (
<div
className={clsx(
'footer-bar absolute bottom-0 z-10 flex h-12 w-full items-center px-4',
'shadow-xs bg-gray-100 transition-opacity duration-300',
isHoveredAnim && 'hover-bar-anim',
hoveredBookKey === bookKey ? `opacity-100` : `opacity-0`,
)}
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<button className='btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0' onClick={handleGoPrev}>
<RiArrowLeftWideLine size={20} />
</button>
<span className='mx-2 text-center text-sm text-black'>
{progress ? `${Math.round(progress * 100)}%` : ''}
</span>
<input
type='range'
className='mx-2 w-full'
min={0}
max={100}
value={progress ? progress * 100 : 0}
onChange={(e) => handleProgressChange(e)}
/>
<button className='btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0' onClick={handleGoNext}>
<RiArrowRightWideLine size={20} />
</button>
</div>
);
};
export default FooterBar;
@@ -0,0 +1,66 @@
import React from 'react';
import clsx from 'clsx';
import { VscLayoutSidebarLeft, VscLayoutSidebarLeftOff } from 'react-icons/vsc';
interface HeaderBarProps {
bookKey: string;
bookTitle: string;
isHoveredAnim: boolean;
hoveredBookKey: string;
isSideBarVisible: boolean;
sideBarBookKey: string | null;
setSideBarVisibility: (visibility: boolean) => void;
setSideBarBookKey: (key: string) => void;
setHoveredBookKey: (key: string) => void;
}
const HeaderBar: React.FC<HeaderBarProps> = ({
bookKey,
bookTitle,
isHoveredAnim,
hoveredBookKey,
isSideBarVisible,
sideBarBookKey,
setSideBarVisibility,
setSideBarBookKey,
setHoveredBookKey,
}) => {
const toggleSideBar = () => {
if (!isSideBarVisible) {
setSideBarVisibility(true);
} else if (sideBarBookKey === bookKey) {
setSideBarVisibility(false);
}
setSideBarBookKey(bookKey);
};
return (
<div
className={clsx(
`header-bar absolute top-0 z-10 flex h-10 w-full items-center px-4`,
`shadow-xs bg-gray-100 transition-opacity duration-300`,
isHoveredAnim && 'hover-bar-anim',
hoveredBookKey === bookKey ? `opacity-100` : `opacity-0`,
)}
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<div className='absolute inset-0 flex items-center justify-center'>
<h2 className='line-clamp-1 max-w-[90%] 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}>
{sideBarBookKey === bookKey && isSideBarVisible ? (
<VscLayoutSidebarLeft size={16} />
) : (
<VscLayoutSidebarLeftOff size={16} />
)}
</button>
</div>
</div>
);
};
export default HeaderBar;
@@ -0,0 +1,28 @@
import React from 'react';
interface PageInfoProps {
bookFormat: string;
section?: { current: number; total: number };
pageinfo?: { current: number; total: number };
}
const PageInfo: React.FC<PageInfoProps> = ({ bookFormat, section, pageinfo }) => {
const pageInfo =
bookFormat === 'PDF'
? section
? `${section.current + 1} / ${section.total}`
: ''
: pageinfo
? `Loc. ${pageinfo.current + 1} / ${pageinfo.total}`
: '';
return (
<div className='pageinfo absolute bottom-0 left-0 right-0 flex h-12 items-center justify-center'>
<h2 className='px-2 text-center font-sans text-xs font-extralight text-slate-500'>
{pageInfo}
</h2>
</div>
);
};
export default PageInfo;
@@ -3,8 +3,6 @@
import * as React from 'react';
import { useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { VscLayoutSidebarLeft, VscLayoutSidebarLeftOff } from 'react-icons/vsc';
import { RiArrowLeftWideLine, RiArrowRightWideLine } from 'react-icons/ri';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore, DEFAULT_BOOK_STATE } from '@/store/readerStore';
@@ -12,6 +10,9 @@ import { useReaderStore, DEFAULT_BOOK_STATE } from '@/store/readerStore';
import Spinner from '@/components/Spinner';
import FoliateViewer from './FoliateViewer';
import SideBar from './SideBar';
import PageInfo from './PageInfo';
import HeaderBar from './HeaderBar';
import FooterBar from './FooterBar';
const ReaderContent = () => {
const router = useRouter();
@@ -20,7 +21,6 @@ const ReaderContent = () => {
const { envConfig } = useEnv();
const { books, settings, initBookState, saveConfig, saveSettings } = useReaderStore();
const { getFoliateView } = useReaderStore();
const [sideBarWidth, setSideBarWidth] = useState(
settings.globalReadSettings.sideBarWidth ?? '25%',
@@ -33,8 +33,7 @@ const ReaderContent = () => {
const getKey = (id: string, index: number) => `${id}-${index}`;
const bookStates = ids.map((id, index) => books[getKey(id, index)] || DEFAULT_BOOK_STATE);
const [sideBarBookKey, setSideBarBookKey] = useState(getKey(ids[0] ?? '', 0));
let sliderProgress = 0;
const [hoveredBookKey, setHoveredBookKey] = useState('');
React.useEffect(() => {
if (ids.length === 0) return;
@@ -49,12 +48,12 @@ const ReaderContent = () => {
});
}, [ids, settings]);
const handleResize = (newWidth: string) => {
const handleSideBarResize = (newWidth: string) => {
setSideBarWidth(newWidth);
settings.globalReadSettings.sideBarWidth = newWidth;
};
const handleTogglePin = () => {
const handleSideBarTogglePin = () => {
if (isSideBarPinned && isSideBarVisible) {
setSideBarVisibility(false);
}
@@ -73,37 +72,19 @@ const ReaderContent = () => {
router.back();
};
const handleProgressChange = (bookKey: string, event: React.ChangeEvent) => {
console.log('progress change', bookKey, event);
const newProgress = parseInt((event.target as HTMLInputElement).value, 10);
sliderProgress = newProgress;
const foliateView = getFoliateView(bookKey);
foliateView?.goToFraction(newProgress / 100.0);
};
const handleGoPrev = (bookKey: string) => {
const foliateView = getFoliateView(bookKey);
foliateView?.goLeft();
};
const handleGoNext = (bookKey: string) => {
const foliateView = getFoliateView(bookKey);
foliateView?.goRight();
};
const getGridTemplate = () => {
const count = ids.length;
const aspectRatio = window.innerWidth / window.innerHeight;
if (count === 1) {
if (count <= 1) {
// One book, full screen
return { columns: '1fr', rows: '1fr' };
} else if (count === 2) {
if (aspectRatio < 1) {
// portrait mode: vertical split
// portrait mode: horizontal split
return { columns: '1fr', rows: '1fr 1fr' };
} else {
// landscape mode: horizontal split
// landscape mode: vertical split
return { columns: '1fr 1fr', rows: '1fr' };
}
} else if (count === 3 || count === 4) {
@@ -138,8 +119,8 @@ const ReaderContent = () => {
width={sideBarWidth}
isVisible={isSideBarVisible}
isPinned={isSideBarPinned}
onResize={handleResize}
onTogglePin={handleTogglePin}
onResize={handleSideBarResize}
onTogglePin={handleSideBarTogglePin}
onGoToLibrary={handleCloseBook}
onSetVisibility={(visibility: boolean) => setSideBarVisibility(visibility)}
/>
@@ -157,76 +138,28 @@ const ReaderContent = () => {
const { book, config, bookDoc } = bookState;
if (!book || !config || !bookDoc) return null;
const { section, pageinfo, progress } = config;
const pageInfo =
book.format === 'PDF'
? section
? `${section.current + 1} / ${section.total}`
: ''
: pageinfo
? `Loc. ${pageinfo.current + 1} / ${pageinfo.total}`
: '';
const progressInfo = progress ? `${Math.round(progress * 100)}%` : '';
sliderProgress = progress ? progress * 100 : 0;
return (
<div key={key} className='relative h-full w-full overflow-hidden'>
<div
className={`absolute top-0 z-10 flex h-10 w-full items-center px-4 ${
ids.length > 1 ? 'header-bar-anim' : 'header-bar'
} shadow-xs opacity-0 transition-opacity duration-300 hover:opacity-100`}
>
<div className='absolute inset-0 flex items-center justify-center'>
<h2 className='line-clamp-1 max-w-[90%] px-2 text-center text-xs font-semibold'>
{book?.title}
</h2>
</div>
<div className='absolute left-4 flex h-full items-center p-2'>
<button
onClick={() => {
if (!isSideBarVisible) {
setSideBarVisibility(true);
} else if (sideBarBookKey === key) {
setSideBarVisibility(false);
}
setSideBarBookKey(key);
}}
>
{sideBarBookKey == key && isSideBarVisible ? (
<VscLayoutSidebarLeft size={16} />
) : (
<VscLayoutSidebarLeftOff size={16} />
)}
</button>
</div>
</div>
<HeaderBar
bookKey={key}
bookTitle={book?.title}
isHoveredAnim={ids.length > 1}
hoveredBookKey={hoveredBookKey}
isSideBarVisible={isSideBarVisible}
sideBarBookKey={sideBarBookKey}
setSideBarVisibility={setSideBarVisibility}
setSideBarBookKey={setSideBarBookKey}
setHoveredBookKey={setHoveredBookKey}
/>
<FooterBar
bookKey={key}
progress={progress}
isHoveredAnim={ids.length > 1}
hoveredBookKey={hoveredBookKey}
setHoveredBookKey={setHoveredBookKey}
/>
<FoliateViewer bookKey={key} bookDoc={bookDoc!} bookConfig={config!} />
<div className='pageinfo absolute bottom-0 left-0 right-0 flex h-12 items-center justify-center'>
<h2 className='px-2 text-center font-sans text-xs font-extralight text-slate-500'>
{pageInfo}
</h2>
</div>
<div className='footer-bar shadow-xs absolute bottom-0 flex h-12 w-full items-center bg-gray-100 px-4 opacity-0 transition-opacity duration-300 hover:opacity-100'>
<button
className='btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0'
onClick={() => handleGoPrev(key)}
>
<RiArrowLeftWideLine size={20} />
</button>
<span className='mx-2 text-center text-sm text-black'>{progressInfo}</span>
<input
type='range'
className='mx-2 w-full'
min={0}
max={100}
value={sliderProgress}
onChange={(e) => handleProgressChange(key, e)}
/>
<button
className='btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0'
onClick={() => handleGoNext(key)}
>
<RiArrowRightWideLine size={20} />
</button>
</div>
<PageInfo bookFormat={book.format} section={section} pageinfo={pageinfo} />;
</div>
);
})}
@@ -1,4 +1,3 @@
import { Book } from '@/types/book';
import React, { useState, useEffect } from 'react';
import {
MdSearch,
+235 -391
View File
File diff suppressed because it is too large Load Diff