settings: more reasonable constraints for top and bottom margins (#1428)

This commit is contained in:
Huang Xin
2025-06-19 23:01:20 +08:00
committed by GitHub
parent 0f66ecbba1
commit 31aeb2289c
8 changed files with 91 additions and 27 deletions
@@ -29,7 +29,8 @@ interface BooksGridProps {
const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
const { appService } = useEnv();
const { getConfig, getBookData } = useBookDataStore();
const { getProgress, getViewState, getViewSettings, hoveredBookKey } = useReaderStore();
const { getProgress, getViewState, getViewSettings } = useReaderStore();
const { getGridInsets, setGridInsets, hoveredBookKey } = useReaderStore();
const { isSideBarVisible, sideBarBookKey } = useSidebarStore();
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore();
@@ -45,6 +46,21 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sideBarBookKey]);
useEffect(() => {
if (!screenInsets) return;
bookKeys.forEach((bookKey, index) => {
const { top, right, bottom, left } = getInsetEdges(index, bookKeys.length, aspectRatio);
const gridInsets = {
top: top ? screenInsets.top : 0,
right: right ? screenInsets.right : 0,
bottom: bottom ? screenInsets.bottom : 0,
left: left ? screenInsets.left : 0,
};
setGridInsets(bookKey, gridInsets);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookKeys, screenInsets]);
if (!screenInsets) return null;
return (
@@ -56,26 +72,18 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
}}
>
{bookKeys.map((bookKey, index) => {
const { top, right, bottom, left } = getInsetEdges(index, bookKeys.length, aspectRatio);
const bookData = getBookData(bookKey);
const config = getConfig(bookKey);
const progress = getProgress(bookKey);
const viewSettings = getViewSettings(bookKey);
const gridInsets = getGridInsets(bookKey);
const { book, bookDoc } = bookData || {};
if (!book || !config || !bookDoc || !viewSettings) return null;
if (!book || !config || !bookDoc || !viewSettings || !gridInsets) return null;
const { section, pageinfo, timeinfo, sectionLabel } = progress || {};
const isBookmarked = getViewState(bookKey)?.ribbonVisible;
const horizontalGapPercent = viewSettings.gapPercent;
const viewInsets = getViewInsets(viewSettings);
// insets for the grid cell
const gridInsets = {
top: top ? screenInsets.top : 0,
right: right ? screenInsets.right : 0,
bottom: bottom ? screenInsets.bottom : 0,
left: left ? screenInsets.left : 0,
};
// insets for the content inside the grid cell
const contentInsets = {
top: gridInsets.top + viewInsets.top,
right: gridInsets.right + viewInsets.right,
@@ -110,7 +118,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
bookKey={bookKey}
bookDoc={bookDoc}
config={config}
insets={contentInsets}
contentInsets={contentInsets}
/>
{viewSettings.vertical && viewSettings.scrolled && (
<>
@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
import { BookDoc, getDirection } from '@/libs/document';
import { BookConfig } from '@/types/book';
import { FoliateView, wrappedFoliateView } from '@/types/view';
import { Insets } from '@/types/misc';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
import { useReaderStore } from '@/store/readerStore';
@@ -38,8 +39,8 @@ const FoliateViewer: React.FC<{
bookKey: string;
bookDoc: BookDoc;
config: BookConfig;
insets: { top: number; right: number; bottom: number; left: number };
}> = ({ bookKey, bookDoc, config, insets }) => {
contentInsets: Insets;
}> = ({ bookKey, bookDoc, config, contentInsets: insets }) => {
const { getView, setView: setFoliateView, setProgress } = useReaderStore();
const { getViewSettings, setViewSettings } = useReaderStore();
const { getParallels } = useParallelViewStore();
@@ -248,11 +249,16 @@ const FoliateViewer: React.FC<{
const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder;
const showDoubleBorderHeader = showDoubleBorder && viewSettings.showHeader;
const showDoubleBorderFooter = showDoubleBorder && viewSettings.showFooter;
const showTopHeader = viewSettings.showHeader && !viewSettings.vertical;
const showBottomFooter = viewSettings.showFooter && !viewSettings.vertical;
const moreTopInset = showTopHeader ? Math.max(0, 44 - insets.top) : 0;
const moreBottomInset = showBottomFooter ? Math.max(0, 44 - insets.bottom) : 0;
const moreRightInset = showDoubleBorderHeader ? 32 : 0;
const moreLeftInset = showDoubleBorderFooter ? 32 : 0;
viewRef.current?.renderer.setAttribute('margin-top', `${insets.top}px`);
viewRef.current?.renderer.setAttribute('margin-top', `${insets.top + moreTopInset}px`);
viewRef.current?.renderer.setAttribute('margin-right', `${insets.right + moreRightInset}px`);
viewRef.current?.renderer.setAttribute('margin-bottom', `${insets.bottom}px`);
viewRef.current?.renderer.setAttribute('margin-bottom', `${insets.bottom + moreBottomInset}px`);
viewRef.current?.renderer.setAttribute('margin-left', `${insets.left + moreLeftInset}px`);
viewRef.current?.renderer.setAttribute('gap', `${viewSettings.gapPercent}%`);
if (viewSettings.scrolled) {
@@ -1,6 +1,8 @@
import clsx from 'clsx';
import React, { useEffect, useRef } from 'react';
import { Insets } from '@/types/misc';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
import { eventDispatcher } from '@/utils/event';
interface SectionInfoProps {
@@ -22,6 +24,13 @@ const HintInfo: React.FC<SectionInfoProps> = ({
contentInsets,
gridInsets,
}) => {
const { appService } = useEnv();
const { systemUIVisible, statusBarHeight } = useThemeStore();
const topInset = Math.max(
gridInsets.top,
appService?.isAndroidApp && systemUIVisible ? statusBarHeight / 2 : 0,
);
const [hintMessage, setHintMessage] = React.useState<string | null>(null);
const hintTimeout = useRef(2000);
const dismissTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -57,7 +66,7 @@ const HintInfo: React.FC<SectionInfoProps> = ({
hintMessage ? 'bg-base-100' : 'bg-transparent',
)}
style={{
height: `${gridInsets.top}px`,
height: `${topInset}px`,
}}
/>
<div
@@ -83,7 +92,7 @@ const HintInfo: React.FC<SectionInfoProps> = ({
width: showDoubleBorder ? '30px' : `${horizontalGap}%`,
}
: {
top: `${gridInsets.top}px`,
top: `${topInset}px`,
insetInlineEnd: `calc(${horizontalGap / 2}% + ${contentInsets.right}px)`,
}
}
@@ -1,6 +1,8 @@
import clsx from 'clsx';
import React from 'react';
import { Insets } from '@/types/misc';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
interface SectionInfoProps {
section?: string;
@@ -21,6 +23,13 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
contentInsets,
gridInsets,
}) => {
const { appService } = useEnv();
const { systemUIVisible, statusBarHeight } = useThemeStore();
const topInset = Math.max(
gridInsets.top,
appService?.isAndroidApp && systemUIVisible ? statusBarHeight / 2 : 0,
);
return (
<>
<div
@@ -29,7 +38,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
isScrolled && !isVertical && 'bg-base-100',
)}
style={{
height: `${gridInsets.top}px`,
height: `${topInset}px`,
}}
/>
<div
@@ -49,7 +58,7 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
height: `calc(100% - ${contentInsets.top + contentInsets.bottom}px)`,
}
: {
top: `${gridInsets.top}px`,
top: `${topInset}px`,
insetInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
width: `calc(100% - ${contentInsets.left + contentInsets.right}px)`,
}
@@ -18,11 +18,12 @@ import NumberInput from './NumberInput';
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { getView, getViewSettings, setViewSettings } = useReaderStore();
const { getView, getViewSettings, getGridInsets, setViewSettings } = useReaderStore();
const { getBookData } = useBookDataStore();
const view = getView(bookKey);
const bookData = getBookData(bookKey)!;
const viewSettings = getViewSettings(bookKey)!;
const gridInsets = getGridInsets(bookKey)!;
const [paragraphMargin, setParagraphMargin] = useState(viewSettings.paragraphMargin!);
const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!);
@@ -273,7 +274,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
useEffect(() => {
if (showHeader === viewSettings.showHeader) return;
if (showHeader && !viewSettings.vertical) {
viewSettings.marginTopPx = Math.max(viewSettings.marginTopPx, 44);
const minMarginTop = Math.max(0, Math.round((44 - gridInsets.top) / 4) * 4);
viewSettings.marginTopPx = Math.max(viewSettings.marginTopPx, minMarginTop);
setMarginTopPx(viewSettings.marginTopPx);
setViewSettings(bookKey, viewSettings);
}
@@ -285,7 +287,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
useEffect(() => {
if (showFooter === viewSettings.showFooter) return;
if (showFooter && !viewSettings.vertical) {
viewSettings.marginBottomPx = Math.max(viewSettings.marginBottomPx, 44);
const minMarginBottom = Math.max(0, Math.round((44 - gridInsets.bottom) / 4) * 4);
viewSettings.marginBottomPx = Math.max(viewSettings.marginBottomPx, minMarginBottom);
setMarginBottomPx(viewSettings.marginBottomPx);
setViewSettings(bookKey, viewSettings);
}
@@ -462,7 +465,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
label={_('Top Margin (px)')}
value={showHeader && !isVertical ? marginTopPx : compactMarginTopPx}
onChange={showHeader && !isVertical ? setMarginTopPx : setCompactMarginTopPx}
min={0}
min={
showHeader && !isVertical
? Math.max(0, Math.round((44 - gridInsets.top) / 4) * 4)
: 0
}
max={88}
step={4}
/>
@@ -470,7 +477,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
label={_('Bottom Margin (px)')}
value={showFooter && !isVertical ? marginBottomPx : compactMarginBottomPx}
onChange={showFooter && !isVertical ? setMarginBottomPx : setCompactMarginBottomPx}
min={0}
min={
showFooter && !isVertical
? Math.max(0, Math.round((44 - gridInsets.bottom) / 4) * 4)
: 0
}
max={88}
step={4}
/>
@@ -133,7 +133,9 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item }) => {
>
<div className='flex cursor-default items-center justify-between'>
<div className='flex items-center'>
<span className='text-gray-500'>{dayjs(item.createdAt).fromNow()}</span>
<span className='text-sm text-gray-500 sm:text-xs'>
{dayjs(item.createdAt).fromNow()}
</span>
</div>
<div className='flex items-center justify-end space-x-3 p-2' dir='ltr'>
{item.note && (
+1 -1
View File
@@ -192,7 +192,7 @@ const Dialog: React.FC<DialogProps> = ({
>
<div className='bg-base-content/50 h-1 w-10 rounded-full'></div>
</div>
<div className='dialog-header bg-base-100 sticky top-1 z-10 flex items-center justify-between px-4'>
<div className='dialog-header bg-base-100 sticky top-1 z-10 flex items-center justify-between px-2 sm:px-4'>
{header ? (
header
) : (
+19
View File
@@ -8,6 +8,7 @@ import {
ViewSettings,
TimeInfo,
} from '@/types/book';
import { Insets } from '@/types/misc';
import { EnvConfigType } from '@/services/environment';
import { FoliateView } from '@/types/view';
import { DocumentLoader, TOCItem } from '@/libs/document';
@@ -27,6 +28,7 @@ interface ViewState {
progress: BookProgress | null;
ribbonVisible: boolean;
ttsEnabled: boolean;
gridInsets: Insets | null;
/* View settings for the view:
generally view settings have a hierarchy of global settings < book settings < view settings
view settings for primary view are saved to book config which is persisted to config file
@@ -67,6 +69,8 @@ interface ReaderStore {
) => Promise<void>;
clearViewState: (key: string) => void;
getViewState: (key: string) => ViewState | null;
getGridInsets: (key: string) => Insets | null;
setGridInsets: (key: string, insets: Insets | null) => void;
}
export const useReaderStore = create<ReaderStore>((set, get) => ({
@@ -114,6 +118,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
progress: null,
ribbonVisible: false,
ttsEnabled: false,
gridInsets: null,
viewSettings: null,
},
},
@@ -159,6 +164,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
progress: null,
ribbonVisible: false,
ttsEnabled: false,
gridInsets: null,
viewSettings: { ...globalViewSettings, ...configViewSettings },
},
},
@@ -178,6 +184,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
progress: null,
ribbonVisible: false,
ttsEnabled: false,
gridInsets: null,
viewSettings: null,
},
},
@@ -306,4 +313,16 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
},
},
})),
getGridInsets: (key: string) => get().viewStates[key]?.gridInsets || null,
setGridInsets: (key: string, insets: Insets | null) =>
set((state) => ({
viewStates: {
...state.viewStates,
[key]: {
...state.viewStates[key]!,
gridInsets: insets,
},
},
})),
}));