From ff714972092affddfa40bf226871efb3059fd3e4 Mon Sep 17 00:00:00 2001 From: chrox Date: Mon, 4 Nov 2024 15:36:17 +0100 Subject: [PATCH] Add bookmark toggler and ribbon --- .../src/app/reader/components/BookGrid.tsx | 18 +++--- .../app/reader/components/BookmarkToggler.tsx | 63 +++++++++++++++++++ .../src/app/reader/components/HeaderBar.tsx | 24 ++----- .../src/app/reader/components/Ribbon.tsx | 25 ++++++++ .../app/reader/components/SidebarToggler.tsx | 39 +++++++----- apps/readest-app/src/store/readerStore.ts | 18 +++++- apps/readest-app/src/types/book.ts | 26 +++++--- 7 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 apps/readest-app/src/app/reader/components/BookmarkToggler.tsx create mode 100644 apps/readest-app/src/app/reader/components/Ribbon.tsx diff --git a/apps/readest-app/src/app/reader/components/BookGrid.tsx b/apps/readest-app/src/app/reader/components/BookGrid.tsx index 881e0c9c..815ebd5a 100644 --- a/apps/readest-app/src/app/reader/components/BookGrid.tsx +++ b/apps/readest-app/src/app/reader/components/BookGrid.tsx @@ -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 = ({ 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 = ({ 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 = ({ bookKeys, bookStates, onCloseBook } return (
+ {isBookmarked && } = ({ 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 ( + + ); +}; + +export default BookmarkToggler; diff --git a/apps/readest-app/src/app/reader/components/HeaderBar.tsx b/apps/readest-app/src/app/reader/components/HeaderBar.tsx index fc95d518..cb7db09c 100644 --- a/apps/readest-app/src/app/reader/components/HeaderBar.tsx +++ b/apps/readest-app/src/app/reader/components/HeaderBar.tsx @@ -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 = ({ }) => { const headerRef = useRef(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 = ({ onMouseEnter={() => setHoveredBookKey(bookKey)} onMouseLeave={() => setHoveredBookKey('')} > -
- toggleSidebarForBook(bookKey)} - > +
+ +
diff --git a/apps/readest-app/src/app/reader/components/Ribbon.tsx b/apps/readest-app/src/app/reader/components/Ribbon.tsx new file mode 100644 index 00000000..5d014d76 --- /dev/null +++ b/apps/readest-app/src/app/reader/components/Ribbon.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +interface RibbonProps { + width: string; +} + +const Ribbon: React.FC = ({ width }) => { + return ( +
+ + + +
+ ); +}; + +export default Ribbon; diff --git a/apps/readest-app/src/app/reader/components/SidebarToggler.tsx b/apps/readest-app/src/app/reader/components/SidebarToggler.tsx index 63496da1..9d2550f8 100644 --- a/apps/readest-app/src/app/reader/components/SidebarToggler.tsx +++ b/apps/readest-app/src/app/reader/components/SidebarToggler.tsx @@ -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 = ({ - isSidebarVisible, - isCurrentBook, - toggleSidebar, -}) => ( - -); +const SidebarToggler: React.FC = ({ bookKey }) => { + const { isSideBarVisible, setSideBarBookKey, sideBarBookKey, toggleSideBar } = useReaderStore(); + const handleToggleSidebar = () => { + if (sideBarBookKey === bookKey) { + toggleSideBar(); + } else { + setSideBarBookKey(bookKey); + if (!isSideBarVisible) toggleSideBar(); + } + }; + return ( + + ); +}; export default SidebarToggler; diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index b32ddea7..c6b11290 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -24,6 +24,7 @@ interface ReaderStore { books: Record; foliateViews: Record; bookDocCache: Record; + bookmarkRibbons: Record; 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((set, get) => ({ books: {}, foliateViews: {}, bookDocCache: {}, + bookmarkRibbons: {}, hoveredBookKey: null, sideBarBookKey: null, @@ -274,7 +278,15 @@ export const useReaderStore = create((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((set, get) => ({ config: { ...book.config, lastUpdated: Date.now(), - bookmarks: [...(book.config?.bookmarks || []), bookmark], + bookmarks, }, }, }, diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 6b383856..eab81b12 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -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 {