Refactoring ReaderContent into FooterBar and HeaderBar
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+235
-391
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user