Add bookmark toggler and ribbon

This commit is contained in:
chrox
2024-11-04 15:36:17 +01:00
parent 1cbceeee49
commit ff71497209
7 changed files with 162 additions and 51 deletions
@@ -1,13 +1,15 @@
import React from 'react';
import { BookState, useReaderStore } from '@/store/readerStore';
import HeaderBar from './HeaderBar';
import FoliateViewer from './FoliateViewer';
import PageInfo from './PageInfo';
import FooterBar from './FooterBar';
import SectionInfo from './SectionInfo';
import getGridTemplate from '@/utils/grid';
import SettingsDialog from './settings/SettingsDialog';
import FoliateViewer from './FoliateViewer';
import getGridTemplate from '@/utils/grid';
import SectionInfo from './SectionInfo';
import HeaderBar from './HeaderBar';
import FooterBar from './FooterBar';
import PageInfo from './PageInfo';
import Ribbon from './Ribbon';
interface BookGridProps {
bookKeys: string[];
@@ -16,7 +18,7 @@ interface BookGridProps {
}
const BookGrid: React.FC<BookGridProps> = ({ bookKeys, bookStates, onCloseBook }) => {
const { isSideBarPinned, isSideBarVisible, sideBarWidth } = useReaderStore();
const { isSideBarPinned, isSideBarVisible, sideBarWidth, bookmarkRibbons } = useReaderStore();
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useReaderStore();
const gridWidth = isSideBarPinned && isSideBarVisible ? `calc(100% - ${sideBarWidth})` : '100%';
const gridTemplate = getGridTemplate(bookKeys.length, window.innerWidth / window.innerHeight);
@@ -32,6 +34,7 @@ const BookGrid: React.FC<BookGridProps> = ({ bookKeys, bookStates, onCloseBook }
>
{bookStates.map((bookState, index) => {
const bookKey = bookKeys[index]!;
const isBookmarked = bookmarkRibbons[bookKey];
const { book, config, bookDoc } = bookState;
if (!book || !config || !bookDoc) return null;
const { section, pageinfo, progress, chapter } = config;
@@ -39,6 +42,7 @@ const BookGrid: React.FC<BookGridProps> = ({ bookKeys, bookStates, onCloseBook }
return (
<div key={bookKey} className='relative h-full w-full overflow-hidden'>
{isBookmarked && <Ribbon width={marginGap} />}
<HeaderBar
bookKey={bookKey}
bookTitle={book.title}
@@ -0,0 +1,63 @@
import React, { useEffect, useState } from 'react';
import { MdOutlineBookmarkAdd, MdOutlineBookmark } from 'react-icons/md';
import * as CFI from 'foliate-js/epubcfi.js';
import { useReaderStore } from '@/store/readerStore';
import { BookNote } from '@/types/book';
interface BookmarkTogglerProps {
bookKey: string;
}
const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
const { books, updateBookmarks, setBookmarkRibbonVisibility } = useReaderStore();
const bookState = books[bookKey]!;
const config = bookState.config!;
const [isBookmarked, setIsBookmarked] = useState(false);
const toggleBookmark = () => {
const { location: cfi, bookmarks = [] } = config;
if (!cfi) return;
if (!isBookmarked) {
setIsBookmarked(true);
const bookmark: BookNote = {
type: 'bookmark',
cfi,
note: '',
created: Date.now(),
};
bookmarks.push(bookmark);
updateBookmarks(bookKey, bookmarks);
} else {
setIsBookmarked(false);
const start = CFI.collapse(cfi);
const end = CFI.collapse(cfi, true);
updateBookmarks(
bookKey,
bookmarks.filter((item) => CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) > 0),
);
}
};
useEffect(() => {
const { location: cfi, bookmarks = [] } = config;
if (!cfi) return;
const start = CFI.collapse(cfi);
const end = CFI.collapse(cfi, true);
const locationBookmarked = bookmarks.some(
(item) => CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) <= 0,
);
setIsBookmarked(locationBookmarked);
setBookmarkRibbonVisibility(bookKey, locationBookmarked);
}, [config]);
return (
<button onClick={toggleBookmark} className='p-2'>
{isBookmarked ? <MdOutlineBookmark size={20} /> : <MdOutlineBookmarkAdd size={20} />}
</button>
);
};
export default BookmarkToggler;
@@ -2,11 +2,12 @@ import clsx from 'clsx';
import React, { useRef, useState } from 'react';
import { PiDotsThreeVerticalBold } from 'react-icons/pi';
import { useReaderStore } from '@/store/readerStore';
import WindowButtons from '@/components/WindowButtons';
import Dropdown from '@/components/Dropdown';
import SidebarToggler from './SidebarToggler';
import BookmarkToggler from './BookmarkToggler';
import ViewMenu from './ViewMenu';
import { useReaderStore } from '@/store/readerStore';
interface HeaderBarProps {
bookKey: string;
@@ -25,17 +26,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
}) => {
const headerRef = useRef<HTMLDivElement>(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { hoveredBookKey, setHoveredBookKey, sideBarBookKey, setSideBarBookKey } = useReaderStore();
const { isSideBarVisible, toggleSideBar } = useReaderStore();
const toggleSidebarForBook = (bookKey: string) => {
if (sideBarBookKey === bookKey) {
toggleSideBar();
} else {
setSideBarBookKey(bookKey);
if (!isSideBarVisible) toggleSideBar();
}
};
const { hoveredBookKey, isSideBarVisible, setHoveredBookKey } = useReaderStore();
const handleToggleDropdown = (isOpen: boolean) => {
setIsDropdownOpen(isOpen);
@@ -56,12 +47,9 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
onMouseEnter={() => setHoveredBookKey(bookKey)}
onMouseLeave={() => setHoveredBookKey('')}
>
<div className='sidebar-toggler mr-auto flex h-full items-center'>
<SidebarToggler
isSidebarVisible={isSideBarVisible}
isCurrentBook={sideBarBookKey === bookKey}
toggleSidebar={() => toggleSidebarForBook(bookKey)}
></SidebarToggler>
<div className='sidebar-bookmark-toggler mr-auto flex h-full items-center'>
<SidebarToggler bookKey={bookKey} />
<BookmarkToggler bookKey={bookKey} />
</div>
<div className='header-title flex flex-1 items-center justify-center'>
@@ -0,0 +1,25 @@
import React from 'react';
interface RibbonProps {
width: string;
}
const Ribbon: React.FC<RibbonProps> = ({ width }) => {
return (
<div className='absolute inset-0 z-10 flex h-11 justify-center' style={{ width }}>
<svg
stroke='currentColor'
fill='currentColor'
strokeWidth='0'
version='1'
viewBox='0 0 20 45'
enableBackground='new 0 0 20 45'
xmlns='http://www.w3.org/2000/svg'
>
<path fill='#F44336' d='M 20 45 L 10 35 L 0 45 L 0 0 L 20 0'></path>
</svg>
</div>
);
};
export default Ribbon;
@@ -1,24 +1,31 @@
import React from 'react';
import { VscLayoutSidebarLeft, VscLayoutSidebarLeftOff } from 'react-icons/vsc';
import { useReaderStore } from '@/store/readerStore';
interface SidebarTogglerProps {
isSidebarVisible: boolean;
isCurrentBook: boolean;
toggleSidebar: () => void;
bookKey: string;
}
const SidebarToggler: React.FC<SidebarTogglerProps> = ({
isSidebarVisible,
isCurrentBook,
toggleSidebar,
}) => (
<button onClick={toggleSidebar} className='p-2'>
{isCurrentBook && isSidebarVisible ? (
<VscLayoutSidebarLeft size={16} />
) : (
<VscLayoutSidebarLeftOff size={16} />
)}
</button>
);
const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
const { isSideBarVisible, setSideBarBookKey, sideBarBookKey, toggleSideBar } = useReaderStore();
const handleToggleSidebar = () => {
if (sideBarBookKey === bookKey) {
toggleSideBar();
} else {
setSideBarBookKey(bookKey);
if (!isSideBarVisible) toggleSideBar();
}
};
return (
<button onClick={handleToggleSidebar} className='p-2'>
{sideBarBookKey == bookKey && isSideBarVisible ? (
<VscLayoutSidebarLeft size={16} />
) : (
<VscLayoutSidebarLeftOff size={16} />
)}
</button>
);
};
export default SidebarToggler;
+15 -3
View File
@@ -24,6 +24,7 @@ interface ReaderStore {
books: Record<string, BookState>;
foliateViews: Record<string, FoliateView>;
bookDocCache: Record<string, BookDoc>;
bookmarkRibbons: Record<string, boolean>;
hoveredBookKey: string | null;
sideBarBookKey: string | null;
@@ -39,6 +40,8 @@ interface ReaderStore {
setSideBarVisibility: (visible: boolean) => void;
setSideBarPin: (pinned: boolean) => void;
setBookmarkRibbonVisibility: (key: string, visible: boolean) => void;
isFontLayoutSettingsDialogOpen: boolean;
isFontLayoutSettingsGlobal: boolean;
setFontLayoutSettingsDialogOpen: (open: boolean) => void;
@@ -70,7 +73,7 @@ interface ReaderStore {
initBookState: (envConfig: EnvConfigType, id: string, key: string, isPrimary?: boolean) => void;
clearBookState: (key: string) => void;
addBookmark: (key: string, bookmark: BookNote) => void;
updateBookmarks: (key: string, bookmarks: BookNote[]) => void;
}
export const DEFAULT_BOOK_STATE = {
@@ -91,6 +94,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
books: {},
foliateViews: {},
bookDocCache: {},
bookmarkRibbons: {},
hoveredBookKey: null,
sideBarBookKey: null,
@@ -274,7 +278,15 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
};
}),
addBookmark: (key: string, bookmark: BookNote) =>
setBookmarkRibbonVisibility: (key: string, visible: boolean) =>
set((state) => ({
bookmarkRibbons: {
...state.bookmarkRibbons,
[key]: visible,
},
})),
updateBookmarks: (key: string, bookmarks: BookNote[]) =>
set((state) => {
const book = state.books[key];
if (!book) return state;
@@ -286,7 +298,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
config: {
...book.config,
lastUpdated: Date.now(),
bookmarks: [...(book.config?.bookmarks || []), bookmark],
bookmarks,
},
},
},
+19 -7
View File
@@ -1,4 +1,16 @@
export type BookFormat = 'EPUB' | 'PDF' | 'MOBI' | 'CBZ' | 'FB2' | 'FBZ';
export type BookNoteType = 'bookmark' | 'highlight' | 'annotation';
export type BookNoteStyle =
| 'underline'
| 'squiggly'
| 'strikethrough'
| 'yellow'
| 'orange'
| 'red'
| 'magenta'
| 'aqua'
| 'lime'
| 'custom';
export interface Book {
hash: string;
@@ -19,14 +31,14 @@ export interface PageInfo {
}
export interface BookNote {
type: BookNoteType;
cfi: string;
start: string;
end: string;
page: number;
noteText?: string;
annotation?: string;
lastModified: number;
removalTimestamp?: number;
text?: string;
style?: string;
customStyle?: string;
note: string;
created: number;
modified?: number;
}
export interface BookLayout {