feat: add an option to show remaining in minutes of the current chapter, closes #406 (#1326)

This commit is contained in:
Huang Xin
2025-06-03 21:37:55 +08:00
committed by GitHub
parent 03b2af1f97
commit 43532a0ee8
26 changed files with 182 additions and 52 deletions
@@ -349,5 +349,8 @@
"Search notes and excerpts...": "بحث في الملاحظات والمقتطفات...",
"Sign in to Sync": "سجّل للمزامنة",
"Synced at {{time}}": "تمت عند {{time}}",
"Never synced": "لم تُزامَن"
"Never synced": "لم تُزامَن",
"Show Remaining Time": "إظهار الوقت المتبقي",
"Show Page Number": "إظهار رقم الصفحة",
"{{time}} min left in chapter": "{{time}} دقيقة متبقية في الفصل"
}
@@ -341,5 +341,8 @@
"Search notes and excerpts...": "Notizen und Auszüge suchen...",
"Sign in to Sync": "Anmelden zum Synchronisieren",
"Synced at {{time}}": "Synchronisiert um {{time}}",
"Never synced": "Nie synchronisiert"
"Never synced": "Nie synchronisiert",
"Show Remaining Time": "Verbleibende Zeit anzeigen",
"Show Page Number": "Seitenzahl anzeigen",
"{{time}} min left in chapter": "{{time}} min verbleibend im Kapitel"
}
@@ -341,5 +341,8 @@
"Search notes and excerpts...": "Αναζήτηση σημειώσεων...",
"Sign in to Sync": "Σύνδεση για συγχρονισμό",
"Synced at {{time}}": "Συγχρονίστηκε στις {{time}}",
"Never synced": "Ποτέ δεν συγχρονίστηκε"
"Never synced": "Ποτέ δεν συγχρονίστηκε",
"Show Remaining Time": "Εμφάνιση υπολειπόμενου χρόνου",
"Show Page Number": "Εμφάνιση αριθμού σελίδας",
"{{time}} min left in chapter": "{{time}} λεπτά απομένουν στο κεφάλαιο"
}
@@ -343,5 +343,8 @@
"Search notes and excerpts...": "Buscar notas...",
"Sign in to Sync": "Inicia sesión para sincronizar",
"Synced at {{time}}": "Sincronizado a las {{time}}",
"Never synced": "Nunca sincronizado"
"Never synced": "Nunca sincronizado",
"Show Remaining Time": "Mostrar tiempo restante",
"Show Page Number": "Mostrar número de página",
"{{time}} min left in chapter": "{{time}} min restantes en el capítulo"
}
@@ -343,5 +343,8 @@
"Search notes and excerpts...": "Rechercher des notes...",
"Sign in to Sync": "Connectez-vous pour synchroniser",
"Synced at {{time}}": "Synchronisé à {{time}}",
"Never synced": "Jamais synchronisé"
"Never synced": "Jamais synchronisé",
"Show Remaining Time": "Afficher le temps restant",
"Show Page Number": "Afficher le numéro de page",
"{{time}} min left in chapter": "{{time}} min restant dans le chapitre"
}
@@ -341,5 +341,8 @@
"Search notes and excerpts...": "नोट्स खोजें...",
"Sign in to Sync": "सिंक करने के लिए साइन इन करें",
"Synced at {{time}}": "{{time}} पर सिंक किया गया",
"Never synced": "कभी सिंक नहीं किया गया"
"Never synced": "कभी सिंक नहीं किया गया",
"Show Remaining Time": "शेष समय दिखाएं",
"Show Page Number": "पृष्ठ संख्या दिखाएं",
"{{time}} min left in chapter": "{{time}} मिनट शेष अध्याय में"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "Cari catatan...",
"Sign in to Sync": "Masuk untuk Sinkronisasi",
"Synced at {{time}}": "Disinkronkan pada {{time}}",
"Never synced": "Belum pernah disinkronkan"
"Never synced": "Belum pernah disinkronkan",
"Show Remaining Time": "__STRING_NOT_TRANSLATED__",
"Show Page Number": "__STRING_NOT_TRANSLATED__",
"{{time}} min left in chapter": "{{time}} menit tersisa di bab"
}
@@ -343,5 +343,8 @@
"Search notes and excerpts...": "Cerca nelle note...",
"Sign in to Sync": "Accedi per sincronizzare",
"Synced at {{time}}": "Sincronizzato alle {{time}}",
"Never synced": "Mai sincronizzato"
"Never synced": "Mai sincronizzato",
"Show Remaining Time": "Mostra tempo rimanente",
"Show Page Number": "Mostra numero pagina",
"{{time}} min left in chapter": "{{time}} min rimasti nel capitolo"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "ノートを検索...",
"Sign in to Sync": "同期するにはサインイン",
"Synced at {{time}}": "{{time}} に同期済み",
"Never synced": "まだ同期されていません"
"Never synced": "まだ同期されていません",
"Show Remaining Time": "残り時間を表示",
"Show Page Number": "ページ番号を表示",
"{{time}} min left in chapter": "{{time}}分残り"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "노트 검색...",
"Sign in to Sync": "동기화를 위해 로그인하세요",
"Synced at {{time}}": "{{time}}에 동기화됨",
"Never synced": "동기화된 적 없음"
"Never synced": "동기화된 적 없음",
"Show Remaining Time": "남은 시간 표시",
"Show Page Number": "페이지 번호 표시",
"{{time}} min left in chapter": "{{time}}분 남음"
}
@@ -341,5 +341,8 @@
"Search notes and excerpts...": "Zoek notities...",
"Sign in to Sync": "Meld je aan om te synchroniseren",
"Synced at {{time}}": "Gesynchroniseerd om {{time}}",
"Never synced": "Nooit gesynchroniseerd"
"Never synced": "Nooit gesynchroniseerd",
"Show Remaining Time": "Toon resterende tijd",
"Show Page Number": "Toon paginanummer",
"{{time}} min left in chapter": "Nog {{time}} min in hoofdstuk"
}
@@ -345,5 +345,8 @@
"Search notes and excerpts...": "Szukaj notatek...",
"Sign in to Sync": "Zaloguj się",
"Synced at {{time}}": "Sync: {{time}}",
"Never synced": "Brak synchronizacji"
"Never synced": "Brak synchronizacji",
"Show Remaining Time": "Pokazuj pozostały czas",
"Show Page Number": "Pokaż numer strony",
"{{time}} min left in chapter": "{{time}} min pozostało do końca rozdziału"
}
@@ -343,5 +343,8 @@
"Search notes and excerpts...": "Buscar notas...",
"Sign in to Sync": "Entrar",
"Synced at {{time}}": "Sincronizado às {{time}}",
"Never synced": "Nunca sincronizado"
"Never synced": "Nunca sincronizado",
"Show Remaining Time": "Mostrar Tempo Restante",
"Show Page Number": "Mostrar Número da Página",
"{{time}} min left in chapter": "{{time}} min restantes no capítulo"
}
@@ -345,5 +345,8 @@
"Search notes and excerpts...": "Искать записи...",
"Sign in to Sync": "Войти",
"Synced at {{time}}": "Синхр. в {{time}}",
"Never synced": "Не синхр."
"Never synced": "Не синхр.",
"Show Remaining Time": "Показать оставшееся время",
"Show Page Number": "Показать номер страницы",
"{{time}} min left in chapter": "{{time}} мин осталось в главе"
}
@@ -341,5 +341,8 @@
"Search notes and excerpts...": "Notlarda ara...",
"Sign in to Sync": "Giriş yap",
"Synced at {{time}}": "{{time}}'de eşzlendi",
"Never synced": "Hiç eşzlenmedi"
"Never synced": "Hiç eşzlenmedi",
"Show Remaining Time": "Kalan Süreyi Göster",
"Show Page Number": "Sayfa Numarasını Göster",
"{{time}} min left in chapter": "{{time}} dakika bölümde kaldı"
}
@@ -345,5 +345,8 @@
"Search notes and excerpts...": "Шукати нотатки...",
"Sign in to Sync": "Увійти",
"Synced at {{time}}": "Синхр. о {{time}}",
"Never synced": "Ніколи не синхр."
"Never synced": "Ніколи не синхр.",
"Show Remaining Time": "Показати залишок часу",
"Show Page Number": "Показати номер сторінки",
"{{time}} min left in chapter": "{{time}} хв до кінця розділу"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "Tìm ghi chú...",
"Sign in to Sync": "Đăng nhập",
"Synced at {{time}}": "Đã đồng bộ lúc {{time}}",
"Never synced": "Chưa từng đồng bộ"
"Never synced": "Chưa từng đồng bộ",
"Show Remaining Time": "Hiển thị thời gian còn lại",
"Show Page Number": "Hiển thị số trang",
"{{time}} min left in chapter": "{{time}} phút còn lại trong chương"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "搜索笔记...",
"Sign in to Sync": "登录后同步",
"Synced at {{time}}": "同步于:{{time}}",
"Never synced": "从未同步"
"Never synced": "从未同步",
"Show Remaining Time": "显示剩余时间",
"Show Page Number": "显示页码",
"{{time}} min left in chapter": "本章剩余 {{time}} 分钟"
}
@@ -339,5 +339,8 @@
"Search notes and excerpts...": "搜尋筆記...",
"Sign in to Sync": "登入以同步",
"Synced at {{time}}": "同步於:{{time}}",
"Never synced": "從未同步"
"Never synced": "從未同步",
"Show Remaining Time": "顯示剩餘時間",
"Show Page Number": "顯示頁碼",
"{{time}} min left in chapter": "本章剩餘 {{time}} 分鐘"
}
@@ -11,7 +11,7 @@ import getGridTemplate from '@/utils/grid';
import SectionInfo from './SectionInfo';
import HeaderBar from './HeaderBar';
import FooterBar from './FooterBar';
import PageInfoView from './PageInfo';
import ProgressInfoView from './ProgressInfo';
import Ribbon from './Ribbon';
import SettingsDialog from './settings/SettingsDialog';
import Annotator from './annotator/Annotator';
@@ -57,7 +57,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
const { book, bookDoc } = bookData || {};
if (!book || !config || !bookDoc || !viewSettings) return null;
const { section, pageinfo, sectionLabel } = progress || {};
const { section, pageinfo, timeinfo, sectionLabel } = progress || {};
const isBookmarked = getViewState(bookKey)?.ribbonVisible;
const horizontalGapPercent = viewSettings.gapPercent;
const verticalMarginPixels = viewSettings.marginPx;
@@ -139,13 +139,12 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
verticalMargin={verticalMarginPixels}
/>
{showFooter && (
<PageInfoView
<ProgressInfoView
bookKey={bookKey}
bookFormat={book.format}
section={section}
pageinfo={pageinfo}
showDoubleBorder={viewSettings.vertical && viewSettings.doubleBorder}
isScrolled={viewSettings.scrolled}
isVertical={viewSettings.vertical}
timeinfo={timeinfo}
horizontalGap={horizontalGapPercent}
verticalMargin={verticalMarginPixels}
/>
@@ -61,7 +61,15 @@ const FoliateViewer: React.FC<{
const progressRelocateHandler = (event: Event) => {
const detail = (event as CustomEvent).detail;
setProgress(bookKey, detail.cfi, detail.tocItem, detail.section, detail.location, detail.range);
setProgress(
bookKey,
detail.cfi,
detail.tocItem,
detail.section,
detail.location,
detail.time,
detail.range,
);
};
const getDocTransformHandler = ({ width, height }: { width: number; height: number }) => {
@@ -1,32 +1,37 @@
import clsx from 'clsx';
import React from 'react';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { PageInfo } from '@/types/book';
import { PageInfo, TimeInfo } from '@/types/book';
interface PageInfoProps {
bookKey: string;
bookFormat: string;
section?: PageInfo;
pageinfo?: PageInfo;
showDoubleBorder: boolean;
isScrolled: boolean;
isVertical: boolean;
timeinfo?: TimeInfo;
horizontalGap: number;
verticalMargin: number;
}
const PageInfoView: React.FC<PageInfoProps> = ({
const ProgressInfoView: React.FC<PageInfoProps> = ({
bookKey,
bookFormat,
section,
pageinfo,
showDoubleBorder,
isScrolled,
isVertical,
timeinfo,
horizontalGap,
verticalMargin,
}) => {
const _ = useTranslation();
const { appService } = useEnv();
const { getViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
const isScrolled = viewSettings.scrolled;
const isVertical = viewSettings.vertical;
const pageInfo = ['PDF', 'CBZ'].includes(bookFormat)
? section
? isVertical
@@ -39,11 +44,15 @@ const PageInfoView: React.FC<PageInfoProps> = ({
totalPage: pageinfo.total,
})
: '';
const timeInfo = timeinfo
? _('{{time}} min left in chapter', { time: Math.round(timeinfo.section) })
: '';
return (
<div
className={clsx(
'pageinfo absolute bottom-0 flex items-center justify-end',
'pageinfo absolute bottom-0 flex items-center justify-between',
'text-neutral-content font-sans text-xs font-extralight',
isVertical ? 'writing-vertical-rl' : 'h-12 w-full',
isScrolled && !isVertical && 'bg-base-100',
)}
@@ -53,21 +62,21 @@ const PageInfoView: React.FC<PageInfoProps> = ({
bottom: `${verticalMargin * 1.5}px`,
left: showDoubleBorder ? `calc(${horizontalGap}% - 32px)` : 0,
width: showDoubleBorder ? '32px' : `${horizontalGap}%`,
height: `calc(100% - ${verticalMargin * 2}px)`,
height: `calc(100% - ${verticalMargin * 3}px)`,
}
: {
insetInlineEnd: `${horizontalGap}%`,
paddingInlineStart: `${horizontalGap}%`,
paddingInlineEnd: `${horizontalGap}%`,
paddingBottom: appService?.hasSafeAreaInset
? 'calc(env(safe-area-inset-bottom)*0.67)'
: 0,
}
}
>
<h2 className='text-neutral-content text-right font-sans text-xs font-extralight'>
{pageInfo}
</h2>
{viewSettings.showRemainingTime && <span className='text-start'>{timeInfo}</span>}
{viewSettings.showPageNumber && <span className='ms-auto text-end'>{pageInfo}</span>}
</div>
);
};
export default PageInfoView;
export default ProgressInfoView;
@@ -45,6 +45,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const [showHeader, setShowHeader] = useState(viewSettings.showHeader!);
const [showFooter, setShowFooter] = useState(viewSettings.showFooter!);
const [showBarsOnScroll, setShowBarsOnScroll] = useState(viewSettings.showBarsOnScroll!);
const [showRemainingTime, setShowRemainingTime] = useState(viewSettings.showRemainingTime!);
const [showPageNumber, setShowPageNumber] = useState(viewSettings.showPageNumber!);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'paragraphMargin', paragraphMargin);
@@ -191,6 +193,16 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showBarsOnScroll]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'showRemainingTime', showRemainingTime, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showRemainingTime]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'showPageNumber', showPageNumber, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showPageNumber]);
const applyMarginAndGap = () => {
const isCompact = !showHeader && !showFooter;
const marginPx = isCompact ? viewSettings.compactMarginPx : viewSettings.marginPx;
@@ -475,6 +487,26 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
onChange={() => setShowFooter(!showFooter)}
/>
</div>
<div className='config-item'>
<span className=''>{_('Show Remaining Time')}</span>
<input
type='checkbox'
className='toggle'
checked={showRemainingTime}
disabled={!showFooter}
onChange={() => setShowRemainingTime(!showRemainingTime)}
/>
</div>
<div className='config-item'>
<span className=''>{_('Show Page Number')}</span>
<input
type='checkbox'
className='toggle'
checked={showPageNumber}
disabled={!showFooter}
onChange={() => setShowPageNumber(!showPageNumber)}
/>
</div>
<div className='config-item'>
<span className=''>{_('Apply also in Scrolled Mode')}</span>
<input
+9 -5
View File
@@ -103,11 +103,6 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = {
writingMode: 'auto',
vertical: false,
rtl: false,
doubleBorder: false,
borderColor: 'red',
showHeader: true,
showFooter: true,
showBarsOnScroll: false,
scrollingOverlap: 0,
allowScript: false,
};
@@ -143,6 +138,15 @@ export const DEFAULT_VIEW_CONFIG: ViewConfig = {
sideBarTab: 'toc',
uiLanguage: '',
sortedTOC: false,
doubleBorder: false,
borderColor: 'red',
showHeader: true,
showFooter: true,
showBarsOnScroll: false,
showRemainingTime: false,
showPageNumber: true,
};
export const DEFAULT_TTS_CONFIG: TTSConfig = {
+11 -1
View File
@@ -1,6 +1,13 @@
import { create } from 'zustand';
import { BookContent, BookConfig, PageInfo, BookProgress, ViewSettings } from '@/types/book';
import {
BookContent,
BookConfig,
PageInfo,
BookProgress,
ViewSettings,
TimeInfo,
} from '@/types/book';
import { EnvConfigType } from '@/services/environment';
import { FoliateView } from '@/types/view';
import { DocumentLoader, TOCItem } from '@/libs/document';
@@ -41,6 +48,7 @@ interface ReaderStore {
tocItem: TOCItem,
section: PageInfo,
pageinfo: PageInfo,
timeinfo: TimeInfo,
range: Range,
) => void;
getProgress: (key: string) => BookProgress | null;
@@ -215,6 +223,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
tocItem: TOCItem,
section: PageInfo,
pageinfo: PageInfo,
timeinfo: TimeInfo,
range: Range,
) =>
set((state) => {
@@ -270,6 +279,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
sectionId: tocItem?.id,
section,
pageinfo,
timeinfo,
range,
},
},
+16 -5
View File
@@ -41,6 +41,12 @@ export interface PageInfo {
total: number;
}
// Remaining time of the book in minutes
export interface TimeInfo {
section: number;
total: number;
}
export interface BookNote {
bookHash?: string;
id: string;
@@ -82,11 +88,6 @@ export interface BookLayout {
writingMode: WritingMode;
vertical: boolean;
rtl: boolean;
doubleBorder: boolean;
borderColor: string;
showHeader: boolean;
showFooter: boolean;
showBarsOnScroll: boolean;
scrollingOverlap: number;
allowScript: boolean;
}
@@ -122,6 +123,15 @@ export interface ViewConfig {
sideBarTab: string;
uiLanguage: string;
sortedTOC: boolean;
doubleBorder: boolean;
borderColor: string;
showHeader: boolean;
showFooter: boolean;
showRemainingTime: boolean;
showPageNumber: boolean;
showBarsOnScroll: boolean;
}
export interface TTSConfig {
@@ -156,6 +166,7 @@ export interface BookProgress {
sectionLabel: string;
section: PageInfo;
pageinfo: PageInfo;
timeinfo: TimeInfo;
range: Range;
}