207 lines
6.6 KiB
TypeScript
207 lines
6.6 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { useEffect, useState } from 'react';
|
|
import { useSearchParams, useRouter } from 'next/navigation';
|
|
|
|
import { useEnv } from '@/context/EnvContext';
|
|
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';
|
|
import SectionInfo from './SectionInfo';
|
|
|
|
const ReaderContent = () => {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const ids = (searchParams.get('ids') || '').split(',');
|
|
|
|
const { envConfig } = useEnv();
|
|
const { books, settings, initBookState, saveConfig, saveSettings } = useReaderStore();
|
|
const { getFoliateView } = useReaderStore();
|
|
|
|
const [sideBarWidth, setSideBarWidth] = useState(
|
|
settings.globalReadSettings.sideBarWidth ?? '25%',
|
|
);
|
|
const [isSideBarPinned, setIsSideBarPinned] = useState(
|
|
settings.globalReadSettings.isSideBarPinned ?? true,
|
|
);
|
|
const [isSideBarVisible, setSideBarVisibility] = useState(isSideBarPinned);
|
|
|
|
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));
|
|
const [hoveredBookKey, setHoveredBookKey] = useState('');
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (
|
|
(e.ctrlKey && e.key === 'Tab') ||
|
|
(e.altKey && e.key === 'Tab') ||
|
|
(e.metaKey && e.key === 'Tab')
|
|
) {
|
|
setSideBarBookKey((prevKey) => {
|
|
const index = ids.findIndex((id) => prevKey.startsWith(id));
|
|
const nextIndex = (index + 1) % ids.length;
|
|
return getKey(ids[nextIndex]!, nextIndex);
|
|
});
|
|
e.preventDefault();
|
|
}
|
|
if (e.key === 'ArrowLeft') {
|
|
getFoliateView(sideBarBookKey)?.goLeft();
|
|
} else if (e.key === 'ArrowRight') {
|
|
getFoliateView(sideBarBookKey)?.goRight();
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (ids.length === 0) return;
|
|
const uniqueIds = new Set<string>();
|
|
ids.forEach((id, index) => {
|
|
const isPrimary = !uniqueIds.has(id);
|
|
uniqueIds.add(id);
|
|
const key = getKey(id, index);
|
|
if (books[key]) return;
|
|
console.log('fetching book', key);
|
|
initBookState(envConfig, id, key, isPrimary);
|
|
});
|
|
}, [ids, settings]);
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}, [sideBarBookKey]);
|
|
|
|
const handleSideBarResize = (newWidth: string) => {
|
|
setSideBarWidth(newWidth);
|
|
settings.globalReadSettings.sideBarWidth = newWidth;
|
|
};
|
|
|
|
const handleSideBarTogglePin = () => {
|
|
if (isSideBarPinned && isSideBarVisible) {
|
|
setSideBarVisibility(false);
|
|
}
|
|
setIsSideBarPinned(!isSideBarPinned);
|
|
settings.globalReadSettings.isSideBarPinned = !isSideBarPinned;
|
|
};
|
|
|
|
const handleCloseBook = () => {
|
|
bookStates.forEach((bookState) => {
|
|
const { book, config, isPrimary } = bookState;
|
|
if (isPrimary && book && config) {
|
|
saveConfig(envConfig, book, config);
|
|
}
|
|
});
|
|
saveSettings(envConfig, settings);
|
|
router.back();
|
|
};
|
|
|
|
const getGridTemplate = () => {
|
|
const count = ids.length;
|
|
const aspectRatio = window.innerWidth / window.innerHeight;
|
|
|
|
if (count <= 1) {
|
|
// One book, full screen
|
|
return { columns: '1fr', rows: '1fr' };
|
|
} else if (count === 2) {
|
|
if (aspectRatio < 1) {
|
|
// portrait mode: horizontal split
|
|
return { columns: '1fr', rows: '1fr 1fr' };
|
|
} else {
|
|
// landscape mode: vertical split
|
|
return { columns: '1fr 1fr', rows: '1fr' };
|
|
}
|
|
} else if (count === 3 || count === 4) {
|
|
// Three or four books, 2x2 grid
|
|
return { columns: '1fr 1fr', rows: '1fr 1fr' };
|
|
} else {
|
|
// Five or more books, a 3x3 grid
|
|
return { columns: '1fr 1fr 1fr', rows: '1fr 1fr 1fr' };
|
|
}
|
|
};
|
|
|
|
const bookState = bookStates[0];
|
|
if (bookStates.length !== ids.length || !bookState || !bookState.book || !bookState.bookDoc) {
|
|
return (
|
|
<div className={'flex-1'}>
|
|
<Spinner loading={true} />
|
|
{bookState?.error && (
|
|
<div className='text-center'>
|
|
<h2 className='text-red-500'>{bookState.error}</h2>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const gridWidth = isSideBarPinned && isSideBarVisible ? `calc(100% - ${sideBarWidth})` : '100%';
|
|
|
|
return (
|
|
<div className='flex h-screen'>
|
|
<SideBar
|
|
bookKey={sideBarBookKey}
|
|
width={sideBarWidth}
|
|
isVisible={isSideBarVisible}
|
|
isPinned={isSideBarPinned}
|
|
onResize={handleSideBarResize}
|
|
onTogglePin={handleSideBarTogglePin}
|
|
onGoToLibrary={handleCloseBook}
|
|
onSetVisibility={(visibility: boolean) => setSideBarVisibility(visibility)}
|
|
/>
|
|
|
|
<div
|
|
className='grid h-full'
|
|
style={{
|
|
width: gridWidth,
|
|
gridTemplateColumns: getGridTemplate().columns,
|
|
gridTemplateRows: getGridTemplate().rows,
|
|
}}
|
|
>
|
|
{bookStates.map((bookState, index) => {
|
|
const key = getKey(ids[index]!, index);
|
|
const { book, config, bookDoc } = bookState;
|
|
if (!book || !config || !bookDoc) return null;
|
|
const { section, pageinfo, progress, chapter } = config;
|
|
return (
|
|
<div key={key} className='relative h-full w-full overflow-hidden'>
|
|
<HeaderBar
|
|
bookKey={key}
|
|
bookTitle={book?.title}
|
|
isHoveredAnim={ids.length > 1}
|
|
hoveredBookKey={hoveredBookKey}
|
|
isSideBarVisible={isSideBarVisible}
|
|
sideBarBookKey={sideBarBookKey}
|
|
setSideBarVisibility={setSideBarVisibility}
|
|
setSideBarBookKey={setSideBarBookKey}
|
|
setHoveredBookKey={setHoveredBookKey}
|
|
/>
|
|
<FoliateViewer
|
|
bookKey={key}
|
|
bookDoc={bookDoc!}
|
|
bookConfig={config!}
|
|
handleKeyDown={handleKeyDown}
|
|
/>
|
|
<SectionInfo chapter={chapter} />
|
|
<PageInfo bookFormat={book.format} section={section} pageinfo={pageinfo} />
|
|
<FooterBar
|
|
bookKey={key}
|
|
progress={progress}
|
|
isHoveredAnim={false}
|
|
hoveredBookKey={hoveredBookKey}
|
|
setHoveredBookKey={setHoveredBookKey}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ReaderContent;
|