fix(progress): show correct progress info for subsections in FB2, closes #3136 (#3145)

This commit is contained in:
Huang Xin
2026-02-03 00:53:26 +08:00
committed by GitHub
parent fca3917a12
commit 9cd88fe839
6 changed files with 73 additions and 26 deletions
@@ -203,6 +203,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
{showFooter && (
<ProgressInfoView
bookKey={bookKey}
toc={bookDoc.toc || []}
section={section}
pageinfo={pageinfo}
timeinfo={timeinfo}
@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Insets } from '@/types/misc';
import { PageInfo, TimeInfo } from '@/types/book';
import { useEnv } from '@/context/EnvContext';
@@ -8,9 +8,12 @@ import { useTranslation } from '@/hooks/useTranslation';
import { useBookDataStore } from '@/store/bookDataStore';
import { formatNumber, formatProgress } from '@/utils/progress';
import { saveViewSettings } from '@/helpers/settings';
import { TOCItem } from '@/libs/document';
import { SIZE_PER_LOC, SIZE_PER_TIME_UNIT } from '@/services/constants';
interface PageInfoProps {
bookKey: string;
toc: TOCItem[];
section?: PageInfo;
pageinfo?: PageInfo;
timeinfo?: TimeInfo;
@@ -21,9 +24,9 @@ interface PageInfoProps {
const ProgressInfoView: React.FC<PageInfoProps> = ({
bookKey,
toc,
section,
pageinfo,
timeinfo,
horizontalGap,
contentInsets,
gridInsets,
@@ -31,10 +34,10 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
const _ = useTranslation();
const { envConfig, appService } = useEnv();
const { getBookData } = useBookDataStore();
const { getView, getViewSettings } = useReaderStore();
const view = getView(bookKey);
const { getProgress, getViewSettings } = useReaderStore();
const bookData = getBookData(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const progress = getProgress(bookKey);
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
const isScrolled = viewSettings.scrolled;
@@ -51,23 +54,41 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
const lang = localStorage?.getItem('i18nextLng') || '';
const localize = isVertical && lang.toLowerCase().startsWith('zh');
const progress = bookData?.isFixedLayout ? section : pageinfo;
const progressInfo = formatProgress(progress?.current, progress?.total, template, localize, lang);
const pageInfo = bookData?.isFixedLayout ? section : pageinfo;
const progressInfo = formatProgress(pageInfo?.current, pageInfo?.total, template, localize, lang);
const timeLeft = timeinfo
? _('{{time}} min left in chapter', {
time: formatNumber(Math.round(timeinfo.section), localize, lang),
})
: '';
const { page = 0, pages = 0 } = view?.renderer || {};
const activeHref = useMemo(() => progress?.sectionHref || null, [progress?.sectionHref]);
const activeTOCItem = useMemo(() => {
if (!activeHref) return null;
for (const item of toc) {
if (item.href === activeHref) return item;
const subitem = item.subitems?.find((sub) => sub.href === activeHref);
if (subitem) return subitem;
}
return null;
}, [activeHref, toc]);
const current = pageInfo?.current || 0;
const total = activeTOCItem?.location ? activeTOCItem.location.next : pageInfo?.total || 0;
const pages = Math.max(total - current, 0);
const timeLeft =
total - 1 >= current
? _('{{time}} min left in chapter', {
time: formatNumber(
Math.round((pages * SIZE_PER_LOC) / SIZE_PER_TIME_UNIT),
localize,
lang,
),
})
: '';
const pageLeft =
pages - 1 > page
total - 1 >= current
? localize
? _('{{number}} pages left in chapter', {
number: formatNumber(pages - page - 1, localize, lang),
number: formatNumber(pages, localize, lang),
})
: _('{{count}} pages left in chapter', {
count: pages - page - 1,
count: pages,
})
: '';
@@ -131,8 +152,8 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
aria-label={[
progress
? _('On {{current}} of {{total}} page', {
current: progress.current + 1,
total: progress.total,
current: current + 1,
total: total,
})
: '',
timeLeft,
+2
View File
@@ -26,8 +26,10 @@ export interface SectionItem {
cfi: string;
size: number;
linear: string;
href?: string;
location?: Location;
pageSpread?: 'left' | 'right' | 'center' | '';
subitems?: Array<SectionItem>;
createDocument: () => Promise<Document>;
}
@@ -707,6 +707,9 @@ export const DOUBLE_CLICK_INTERVAL_THRESHOLD_MS = 250;
export const DISABLE_DOUBLE_CLICK_ON_MOBILE = true;
export const LONG_HOLD_THRESHOLD = 500;
export const SIZE_PER_LOC = 1500;
export const SIZE_PER_TIME_UNIT = 1600;
export const CUSTOM_THEME_TEMPLATES = [
{
light: {
+28 -8
View File
@@ -1,6 +1,7 @@
import { ConvertChineseVariant } from '@/types/book';
import { SectionItem, TOCItem, CFI, BookDoc } from '@/libs/document';
import { initSimpleCC, runSimpleCC } from '@/utils/simplecc';
import { SIZE_PER_LOC } from '@/services/constants';
export const findParentPath = (toc: TOCItem[], href: string): TOCItem[] => {
for (const item of toc) {
@@ -69,17 +70,38 @@ export const updateToc = async (
return acc;
}, []);
const totalSize = cumulativeSizes[cumulativeSizes.length - 1] || 0;
const sizePerLoc = 1500;
const totalLocations = Math.floor(totalSize / SIZE_PER_LOC);
sections.forEach((section, index) => {
section.location = {
current: Math.floor(cumulativeSizes[index]! / sizePerLoc),
next: Math.floor((cumulativeSizes[index]! + sizes[index]!) / sizePerLoc),
total: Math.floor(totalSize / sizePerLoc),
current: Math.floor(cumulativeSizes[index]! / SIZE_PER_LOC),
next: Math.floor((cumulativeSizes[index]! + sizes[index]!) / SIZE_PER_LOC),
total: totalLocations,
};
const subitems = section.subitems || [];
subitems.forEach((subitem, subitemIndex) => {
subitem.location = {
current: Math.floor(
(cumulativeSizes[index]! +
subitems.slice(0, subitemIndex).reduce((sum, t) => sum + (t.size || 0), 0)) /
SIZE_PER_LOC,
),
next: Math.floor(
(cumulativeSizes[index]! +
subitems.slice(0, subitemIndex + 1).reduce((sum, t) => sum + (t.size || 0), 0)) /
SIZE_PER_LOC,
),
total: totalLocations,
};
});
});
const sectionsMap = sections.reduce((map: Record<string, SectionItem>, section) => {
map[section.id] = section;
section.subitems?.forEach((subitem) => {
if (subitem.href) {
map[subitem.href] = subitem;
}
});
return map;
}, {});
@@ -112,12 +134,10 @@ const updateTocData = (
item.id ??= index++;
if (item.href) {
const id = bookDoc.splitTOCHref(item.href)[0]!;
const section = sectionsMap[id];
const section = sectionsMap[item.href] || sectionsMap[id];
if (section) {
item.cfi = section.cfi;
// Add location only when toc item is at the same level as the section
// otherwise the location will not be accurate
if (id === item.href || items.length <= sections.length) {
if (id === item.href || items.length <= sections.length || item.href === section.href) {
item.location = section.location;
}
}