feat(ruler): support vertical ruler and transparent ruler (#3076)

This commit is contained in:
Huang Xin
2026-01-25 20:15:47 +01:00
committed by GitHub
parent 2100071991
commit ffc51e75de
7 changed files with 298 additions and 120 deletions
@@ -27,6 +27,7 @@ import BackgroundTextureSelector from './color/BackgroundTextureSelector';
import HighlightColorsEditor from './color/HighlightColorsEditor';
import TTSHighlightStyleEditor, { TTSHighlightStyle } from './color/TTSHighlightStyleEditor';
import CodeHighlightingSettings from './color/CodeHighlightingSettings';
import ReadingRulerSettings from './color/ReadingRulerSettings';
const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
const _ = useTranslation();
@@ -65,6 +66,11 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
settings.globalReadSettings.userHighlightColors || [],
);
const [readingRulerEnabled, setReadingRulerEnabled] = useState(viewSettings.readingRulerEnabled);
const [readingRulerLines, setReadingRulerLines] = useState(viewSettings.readingRulerLines);
const [readingRulerOpacity, setReadingRulerOpacity] = useState(viewSettings.readingRulerOpacity);
const [readingRulerColor, setReadingRulerColor] = useState(viewSettings.readingRulerColor);
const {
textures: customTextures,
addTexture,
@@ -83,6 +89,9 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
invertImgColorInDark: setInvertImgColorInDark,
codeHighlighting: setcodeHighlighting,
codeLanguage: setCodeLanguage,
readingRulerEnabled: setReadingRulerEnabled,
readingRulerLines: setReadingRulerLines,
readingRulerOpacity: setReadingRulerOpacity,
});
setThemeColor('default');
setThemeMode('auto');
@@ -153,6 +162,26 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [backgroundSize]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerEnabled', readingRulerEnabled, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerEnabled]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerLines', readingRulerLines, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerLines]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerOpacity', readingRulerOpacity, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerOpacity]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerColor', readingRulerColor, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerColor]);
const applyBackgroundTexture = () => {
applyTexture(envConfig, selectedTextureId);
document.documentElement.style.setProperty('--bg-texture-opacity', `${backgroundOpacity}`);
@@ -336,6 +365,17 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
onCustomColorsChange={handleCustomTtsColorsChange}
/>
<ReadingRulerSettings
enabled={readingRulerEnabled}
lines={readingRulerLines}
opacity={readingRulerOpacity}
color={readingRulerColor}
onEnabledChange={setReadingRulerEnabled}
onLinesChange={setReadingRulerLines}
onOpacityChange={setReadingRulerOpacity}
onColorChange={setReadingRulerColor}
/>
<CodeHighlightingSettings
codeHighlighting={codeHighlighting}
codeLanguage={codeLanguage}
@@ -74,11 +74,6 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
const [progressStyle, setProgressStyle] = useState(viewSettings.progressStyle);
const [screenOrientation, setScreenOrientation] = useState(viewSettings.screenOrientation);
const [readingRulerEnabled, setReadingRulerEnabled] = useState(viewSettings.readingRulerEnabled);
const [readingRulerLines, setReadingRulerLines] = useState(viewSettings.readingRulerLines);
const [readingRulerOpacity, setReadingRulerOpacity] = useState(viewSettings.readingRulerOpacity);
const [readingRulerColor, setReadingRulerColor] = useState(viewSettings.readingRulerColor);
const resetToDefaults = useResetViewSettings();
const handleReset = () => {
@@ -113,9 +108,6 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
showProgressInfo: setShowProgressInfo,
tapToToggleFooter: setTapToToggleFooter,
showMarginsOnScroll: setShowMarginsOnScroll,
readingRulerEnabled: setReadingRulerEnabled,
readingRulerLines: setReadingRulerLines,
readingRulerOpacity: setReadingRulerOpacity,
});
};
@@ -391,26 +383,6 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [screenOrientation]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerEnabled', readingRulerEnabled, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerEnabled]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerLines', readingRulerLines, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerLines]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerOpacity', readingRulerOpacity, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerOpacity]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'readingRulerColor', readingRulerColor, false, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readingRulerColor]);
const langCode = getBookLangCode(bookData?.bookDoc?.metadata?.language);
const mightBeRTLBook = MIGHT_BE_RTL_LANGS.includes(langCode) || isCJKEnv();
const isVertical = viewSettings.vertical || writingMode.includes('vertical');
@@ -757,71 +729,6 @@ const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRese
</div>
</div>
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Reading Ruler')}</h2>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item'>
<span className=''>{_('Enable Reading Ruler')}</span>
<input
type='checkbox'
className='toggle'
checked={readingRulerEnabled}
onChange={() => setReadingRulerEnabled(!readingRulerEnabled)}
/>
</div>
<NumberInput
label={_('Lines to Highlight')}
value={readingRulerLines}
onChange={setReadingRulerLines}
disabled={!readingRulerEnabled}
min={1}
max={6}
step={1}
/>
<div className='config-item'>
<span className=''>{_('Ruler Color')}</span>
<div className='flex gap-2'>
<button
disabled={!readingRulerEnabled}
className={`btn btn-circle btn-sm bg-yellow-400 hover:bg-yellow-500 ${readingRulerColor === 'yellow' ? 'ring-base-content ring-2 ring-offset-1' : ''} ${!readingRulerEnabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => setReadingRulerColor('yellow')}
/>
<button
disabled={!readingRulerEnabled}
className={`btn btn-circle btn-sm bg-green-400 hover:bg-green-500 ${readingRulerColor === 'green' ? 'ring-base-content ring-2 ring-offset-1' : ''} ${!readingRulerEnabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => setReadingRulerColor('green')}
/>
<button
disabled={!readingRulerEnabled}
className={`btn btn-circle btn-sm bg-blue-400 hover:bg-blue-500 ${readingRulerColor === 'blue' ? 'ring-base-content ring-2 ring-offset-1' : ''} ${!readingRulerEnabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => setReadingRulerColor('blue')}
/>
<button
disabled={!readingRulerEnabled}
className={`btn btn-circle btn-sm bg-violet-400 hover:bg-violet-500 ${readingRulerColor === 'violet' ? 'ring-base-content ring-2 ring-offset-1' : ''} ${!readingRulerEnabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => setReadingRulerColor('violet')}
/>
<button
disabled={!readingRulerEnabled}
className={`btn btn-circle btn-sm bg-rose-400 hover:bg-rose-500 ${readingRulerColor === 'rose' ? 'ring-base-content ring-2 ring-offset-1' : ''} ${!readingRulerEnabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => setReadingRulerColor('rose')}
/>
</div>
</div>
<NumberInput
label={_('Opacity')}
value={readingRulerOpacity}
onChange={setReadingRulerOpacity}
disabled={!readingRulerEnabled}
min={0.1}
max={0.5}
step={0.1}
/>
</div>
</div>
</div>
{appService?.hasOrientationLock && (
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Screen')}</h2>
@@ -0,0 +1,90 @@
import React from 'react';
import { useTranslation } from '@/hooks/useTranslation';
import { ReadingRulerColor } from '@/types/book';
import NumberInput from '../NumberInput';
interface ReadingRulerSettingsProps {
enabled: boolean;
lines: number;
opacity: number;
color: ReadingRulerColor;
onEnabledChange: (enabled: boolean) => void;
onLinesChange: (lines: number) => void;
onOpacityChange: (opacity: number) => void;
onColorChange: (color: ReadingRulerColor) => void;
}
const RULER_COLORS: { value: ReadingRulerColor; className: string; hoverClassName: string }[] = [
{ value: 'transparent', className: 'bg-transparent', hoverClassName: 'hover:bg-transparent' },
{ value: 'yellow', className: 'bg-yellow-400', hoverClassName: 'hover:bg-yellow-500' },
{ value: 'green', className: 'bg-green-400', hoverClassName: 'hover:bg-green-500' },
{ value: 'blue', className: 'bg-blue-400', hoverClassName: 'hover:bg-blue-500' },
{ value: 'rose', className: 'bg-rose-400', hoverClassName: 'hover:bg-rose-500' },
];
const ReadingRulerSettings: React.FC<ReadingRulerSettingsProps> = ({
enabled,
lines,
opacity,
color,
onEnabledChange,
onLinesChange,
onOpacityChange,
onColorChange,
}) => {
const _ = useTranslation();
return (
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Reading Ruler')}</h2>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item'>
<span>{_('Enable Reading Ruler')}</span>
<input
type='checkbox'
className='toggle'
checked={enabled}
onChange={() => onEnabledChange(!enabled)}
/>
</div>
<NumberInput
label={_('Lines to Highlight')}
value={lines}
onChange={onLinesChange}
disabled={!enabled}
min={1}
max={6}
step={1}
/>
<div className='config-item'>
<span>{_('Ruler Color')}</span>
<div className='flex gap-2'>
{RULER_COLORS.map(({ value, className, hoverClassName }) => (
<button
key={value}
disabled={!enabled}
className={`btn btn-circle btn-sm ${className} ${hoverClassName} ${
color === value ? 'ring-base-content ring-2 ring-offset-1' : ''
} ${!enabled ? 'btn-disabled opacity-50' : ''}`}
onClick={() => onColorChange(value)}
/>
))}
</div>
</div>
<NumberInput
label={_('Opacity')}
value={opacity}
onChange={onOpacityChange}
disabled={!enabled}
min={0.1}
max={0.9}
step={0.1}
/>
</div>
</div>
</div>
);
};
export default ReadingRulerSettings;