Add theme and color config

This commit is contained in:
chrox
2024-11-19 19:28:17 +01:00
parent 6fa043f340
commit 15939ce2fc
42 changed files with 582 additions and 220 deletions
@@ -76,7 +76,11 @@ const BookmarkToggler: React.FC<BookmarkTogglerProps> = ({ bookKey }) => {
return (
<button onClick={toggleBookmark} className='p-2'>
{isBookmarked ? <MdOutlineBookmark size={20} /> : <MdOutlineBookmarkAdd size={20} />}
{isBookmarked ? (
<MdOutlineBookmark size={20} className='text-base-content' />
) : (
<MdOutlineBookmarkAdd size={20} className='text-base-content' />
)}
</button>
);
};
@@ -4,6 +4,7 @@ import { BookDoc } from '@/libs/document';
import { BookConfig, BookNote, BookSearchConfig, BookSearchResult } from '@/types/book';
import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
import { useTheme } from '@/hooks/useTheme';
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
import {
handleKeydown,
@@ -61,6 +62,7 @@ const FoliateViewer: React.FC<{
const [viewInited, setViewInited] = useState(false);
const isViewCreated = useRef(false);
const { setView: setFoliateView, setProgress, getViewSettings } = useReaderStore();
const { themeCode } = useTheme();
const progressRelocateHandler = (event: Event) => {
const detail = (event as CustomEvent).detail;
@@ -118,7 +120,6 @@ const FoliateViewer: React.FC<{
await view.open(bookDoc);
const viewSettings = getViewSettings(bookKey)!;
view.renderer.setStyles?.(getStyles(viewSettings));
const isScrolled = viewSettings.scrolled!;
const marginPx = viewSettings.marginPx!;
const gapPercent = viewSettings.gapPercent!;
@@ -146,6 +147,7 @@ const FoliateViewer: React.FC<{
await view.goToFraction(0);
}
setViewInited(true);
view.renderer.setStyles?.(getStyles(viewSettings, themeCode));
window.addEventListener('message', handleClickTurnPage);
};
@@ -42,12 +42,12 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
<button className='btn btn-ghost mx-2 h-8 min-h-8 w-8 p-0' onClick={handleGoPrev}>
<RiArrowLeftWideLine size={20} />
</button>
<span className='mx-2 text-center text-sm text-black'>
<span className='mx-2 text-center text-sm'>
{pageinfoValid ? `${Math.round(progressFraction * 100)}%` : ''}
</span>
<input
type='range'
className='mx-2 w-full'
className='text-base-content mx-2 w-full'
min={0}
max={100}
value={pageinfoValid ? progressFraction * 100 : 0}
@@ -20,9 +20,9 @@ const NotebookToggler: React.FC<NotebookTogglerProps> = ({ bookKey }) => {
return (
<button onClick={handleToggleSidebar} className='p-2'>
{sideBarBookKey == bookKey && isNotebookVisible ? (
<TbLayoutSidebarRightFilled size={20} />
<TbLayoutSidebarRightFilled size={20} className='text-base-content' />
) : (
<TbLayoutSidebarRight size={20} />
<TbLayoutSidebarRight size={20} className='text-base-content' />
)}
</button>
);
@@ -23,7 +23,9 @@ const PageInfoView: React.FC<PageInfoProps> = ({ bookFormat, section, pageinfo,
className='pageinfo absolute bottom-0 left-0 right-0 flex h-12 items-center justify-end'
style={{ paddingRight: gapRight }}
>
<h2 className='text-right font-sans text-xs font-extralight text-slate-500'>{pageInfo}</h2>
<h2 className='text-neutral-content text-right font-sans text-xs font-extralight'>
{pageInfo}
</h2>
</div>
);
};
@@ -90,7 +90,7 @@ const ReaderContent: React.FC<{ settings: SystemSettings }> = ({ settings }) =>
}
return (
<div className='flex h-screen'>
<div className='text-base-content flex h-screen'>
<SideBar onGoToLibrary={handleCloseBooks} />
<BooksGrid bookKeys={bookKeys} onCloseBook={handleCloseBook} />
<Notebook />
@@ -20,7 +20,9 @@ const SectionInfo: React.FC<SectionInfoProps> = ({ section, gapLeft }) => {
)}
style={{ left: gapLeft }}
>
<h2 className='text-center font-sans text-xs font-light text-slate-500'>{section || ''}</h2>
<h2 className='text-neutral-content text-center font-sans text-xs font-light'>
{section || ''}
</h2>
</div>
);
};
@@ -20,9 +20,9 @@ const SidebarToggler: React.FC<SidebarTogglerProps> = ({ bookKey }) => {
return (
<button onClick={handleToggleSidebar} className='p-2'>
{sideBarBookKey == bookKey && isSideBarVisible ? (
<TbLayoutSidebarFilled size={20} />
<TbLayoutSidebarFilled size={20} className='text-base-content' />
) : (
<TbLayoutSidebar size={20} />
<TbLayoutSidebar size={20} className='text-base-content' />
)}
</button>
);
@@ -2,9 +2,12 @@ import clsx from 'clsx';
import React, { useEffect } from 'react';
import { useState } from 'react';
import { BiMoon, BiSun } from 'react-icons/bi';
import { TbSunMoon } from 'react-icons/tb';
import { MdZoomOut, MdZoomIn, MdCheck } from 'react-icons/md';
import MenuItem from '@/components/MenuItem';
import { useReaderStore } from '@/store/readerStore';
import { useTheme, ThemeMode } from '@/hooks/useTheme';
import { getStyles } from '@/utils/style';
interface ViewMenuProps {
@@ -21,7 +24,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
const { getView, getViewSettings, setViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey);
const [isDarkMode, setDarkMode] = useState(viewSettings!.theme === 'dark');
const { themeMode, isDarkMode, themeCode, updateThemeMode } = useTheme();
const [isScrolledMode, setScrolledMode] = useState(viewSettings!.scrolled);
const [isInvertedColors, setInvertedColors] = useState(viewSettings!.invert);
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
@@ -30,7 +33,6 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - 10, 50));
const resetZoom = () => setZoomLevel(100);
const toggleScrolledMode = () => setScrolledMode(!isScrolledMode);
const toggleDarkMode = () => setDarkMode(!isDarkMode);
const toggleInvertedColors = () => setInvertedColors(!isInvertedColors);
const openFontLayoutMenu = () => {
@@ -38,6 +40,16 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
onSetSettingsDialogOpen(true);
};
const cycleThemeMode = () => {
const nextMode: ThemeMode =
themeMode === 'auto' ? 'light' : themeMode === 'light' ? 'dark' : 'auto';
updateThemeMode(nextMode);
};
useEffect(() => {
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
}, [themeCode]);
useEffect(() => {
getView(bookKey)?.renderer.setAttribute('flow', isScrolledMode ? 'scrolled' : 'paginated');
viewSettings!.scrolled = isScrolledMode;
@@ -46,6 +58,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
useEffect(() => {
document.body.classList.toggle('invert', isInvertedColors);
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
}, [isInvertedColors]);
useEffect(() => {
@@ -53,7 +66,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
if (!view) return;
// FIXME: zoom level is not working in paginated mode
if (viewSettings?.scrolled) {
view.renderer.setStyles?.(getStyles(viewSettings!));
view.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
}
viewSettings!.zoomLevel = zoomLevel;
setViewSettings(bookKey, viewSettings!);
@@ -63,7 +76,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
<div
id='exclude-title-bar-mousedown'
tabIndex={0}
className='view-menu dropdown-content dropdown-right no-triangle z-20 mt-1 w-72 border bg-white shadow-2xl'
className='view-menu dropdown-content dropdown-right no-triangle border-base-100 z-20 mt-1 w-72 border shadow-2xl'
>
<div
className={clsx(
@@ -73,14 +86,17 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
>
<button
onClick={zoomOut}
className={clsx('rounded-full p-2 hover:bg-gray-100', !isScrolledMode && 'btn-disabled')}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
!isScrolledMode && 'btn-disabled text-gray-400',
)}
>
<MdZoomOut size={20} />
</button>
<button
className={clsx(
'h-8 min-h-8 w-[50%] rounded-md p-1 text-center hover:bg-gray-100',
!isScrolledMode && 'btn-disabled',
'hover:bg-base-200 text-base-content h-8 min-h-8 w-[50%] rounded-md p-1 text-center',
!isScrolledMode && 'btn-disabled text-gray-400',
)}
onClick={resetZoom}
>
@@ -88,65 +104,49 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
</button>
<button
onClick={zoomIn}
className={clsx('rounded-full p-2 hover:bg-gray-100', !isScrolledMode && 'btn-disabled')}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
!isScrolledMode && 'btn-disabled text-gray-400',
)}
>
<MdZoomIn size={20} />
</button>
</div>
<hr className='my-1' />
<hr className='border-base-200 my-1' />
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={openFontLayoutMenu}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}></span>
<span className='ml-2'>Font & Layout</span>
</div>
<span className='text-sm text-gray-400'>Shift+F</span>
</button>
<MenuItem label='Font & Layout' shortcut='Shift+F' onClick={openFontLayoutMenu} />
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
<MenuItem
label='Scrolled Mode'
shortcut='Shift+J'
icon={isScrolledMode ? <MdCheck size={20} /> : undefined}
onClick={toggleScrolledMode}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isScrolledMode && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>Scrolled Mode</span>
</div>
<span className='text-sm text-gray-400'>Shift+J</span>
</button>
/>
<hr className='my-1' />
<hr className='border-base-200 my-1' />
<button
className='flex w-full items-center rounded-md p-2 hover:bg-gray-100'
onClick={toggleDarkMode}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isDarkMode ? <BiMoon size={20} /> : <BiSun size={20} />}
</span>
</div>
<span className='ml-2'>{isDarkMode ? 'Dark Mode' : 'Light Mode'}</span>
</button>
<button
className={clsx(
'flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100',
!isDarkMode && 'btn-disabled text-gray-400',
)}
<MenuItem
label={
themeMode === 'dark' ? 'Dark Mode' : themeMode === 'light' ? 'Light Mode' : 'Auto Mode'
}
icon={
themeMode === 'dark' ? (
<BiMoon size={20} />
) : themeMode === 'light' ? (
<BiSun size={20} />
) : (
<TbSunMoon size={20} />
)
}
onClick={cycleThemeMode}
/>
<MenuItem
label='Invert Colors in Dark Mode'
icon={isInvertedColors ? <MdCheck size={20} className='text-base-content' /> : undefined}
onClick={toggleInvertedColors}
>
<div className='flex items-center'>
<span style={{ minWidth: '20px' }}>
{isInvertedColors && <MdCheck size={20} className='text-base-content' />}
</span>
<span className='ml-2'>Invert Colors in Dark Mode</span>
</div>
</button>
disabled={!isDarkMode}
/>
</div>
);
};
@@ -14,7 +14,7 @@ const NotebookHeader: React.FC<{
<div className='z-10 flex items-center space-x-3'>
<button
onClick={handleTogglePin}
className={`${isPinned ? 'bg-gray-300' : 'bg-base-300'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
</button>
@@ -48,7 +48,7 @@ const NoteEditor: React.FC<NoteEditorProps> = ({ onSave, onEdit }) => {
};
return (
<div className='note-editor-container mt-2 rounded-md bg-white p-2'>
<div className='note-editor-container bg-base-100 mt-2 rounded-md p-2'>
<div className='flex w-full justify-between space-x-2'>
<div className='settings-content relative w-full'>
<textarea
@@ -145,7 +145,7 @@ const Notebook: React.FC = ({}) => {
<li key={`${index}-${item.id}`} className='my-2'>
<div
tabIndex={0}
className='collapse-arrow border-base-300 collapse border bg-white'
className='collapse-arrow border-base-300 bg-base-100 collapse border'
>
<div
className='collapse-title h-9 min-h-9 p-2 pr-8 text-sm font-medium'
@@ -1,32 +1,87 @@
import React, { useState } from 'react';
import React, { useEffect } from 'react';
import { MdOutlineLightMode, MdOutlineDarkMode } from 'react-icons/md';
import { MdRadioButtonUnchecked, MdRadioButtonChecked } from 'react-icons/md';
import { TbSunMoon } from 'react-icons/tb';
const ColorPanel: React.FC<{ bookKey: string }> = () => {
const [selectedTheme, setSelectedTheme] = useState('Default');
const themes = [
'Default',
'Gray',
'Sepia',
'Grass',
'Cherry',
'Sky',
'Nord',
'Solarized',
'Gruvbox',
];
import { useTheme } from '@/hooks/useTheme';
import { themes } from '@/styles/themes';
import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { themeMode, themeColor, themeCode, isDarkMode, updateThemeMode, updateThemeColor } =
useTheme();
const { getView, getViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
useEffect(() => {
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [themeCode]);
return (
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Color Settings</h2>
<div className='mt-4 grid grid-cols-3 gap-2'>
{themes.map((theme) => (
<div className='flex items-center justify-between'>
<h2 className='font-medium'>Theme Mode</h2>
<div className='flex gap-2'>
<div className='tooltip tooltip-bottom' data-tip='Auto Mode'>
<button
key={theme}
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
onClick={() => setSelectedTheme(theme)}
className={`btn btn-ghost btn-circle ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('auto')}
>
{theme}
<TbSunMoon size={20} />
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip='Light Mode'>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('light')}
>
<MdOutlineLightMode size={20} />
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip='Dark Mode'>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('dark')}
>
<MdOutlineDarkMode size={20} />
</button>
</div>
</div>
</div>
<div>
<h2 className='mb-2 font-medium'>Theme Color</h2>
<div className='grid grid-cols-3 gap-4'>
{themes.map(({ name, label, colors }) => (
<label
key={name}
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg p-4 shadow-md ${
themeColor === name ? 'ring-2 ring-indigo-500 ring-offset-2' : ''
}`}
style={{
backgroundColor: isDarkMode ? colors.dark['base-100'] : colors.light['base-100'],
color: isDarkMode ? colors.dark['base-content'] : colors.light['base-content'],
}}
>
<input
type='radio'
name='theme'
value={name}
checked={themeColor === name}
onChange={() => updateThemeColor(name)}
className='hidden'
/>
{themeColor === name ? (
<MdRadioButtonChecked size={24} />
) : (
<MdRadioButtonUnchecked size={24} />
)}
<span>{label}</span>
</label>
))}
</div>
</div>
@@ -17,10 +17,10 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
return (
<div
tabIndex={0}
className='dropdown-content dropdown-right no-triangle z-20 mt-1 w-44 border bg-white shadow-2xl'
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 w-44 border shadow-2xl'
>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
className='hover:bg-base-200 text-base-content flex w-full items-center justify-between rounded-md p-2'
onClick={handleToggleGlobal}
>
<div className='flex items-center'>
@@ -6,6 +6,7 @@ import FontDropdown from './FontDropDown';
import { MONOSPACE_FONTS, SANS_SERIF_FONTS, SERIF_FONTS } from '@/services/constants';
import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
import { useTheme } from '@/hooks/useTheme';
interface FontFaceProps {
className?: string;
@@ -24,7 +25,7 @@ const handleFontFaceFont = (option: string, family: string) => {
const FontFace = ({ className, family, label, options, selected, onSelect }: FontFaceProps) => (
<div className={clsx('config-item', className)}>
<span className='text-gray-700'>{label}</span>
<span className=''>{label}</span>
<FontDropdown
family={family}
options={options}
@@ -40,6 +41,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [defaultFontSize, setDefaultFontSize] = useState(viewSettings.defaultFontSize!);
const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!);
@@ -56,7 +58,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.defaultFont = defaultFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [defaultFont]);
useEffect(() => {
@@ -66,7 +68,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.defaultFontSize = defaultFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [defaultFontSize]);
useEffect(() => {
@@ -76,7 +78,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.minimumFontSize = minFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [minFontSize]);
useEffect(() => {
@@ -86,7 +88,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.serifFont = serifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [serifFont]);
useEffect(() => {
@@ -96,7 +98,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.sansSerifFont = sansSerifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [sansSerifFont]);
useEffect(() => {
@@ -106,7 +108,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.monospaceFont = monospaceFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [monospaceFont]);
useEffect(() => {
@@ -116,7 +118,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.overrideFont = overrideFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [overrideFont]);
const handleFontFamilyFont = (option: string) => {
@@ -136,8 +138,8 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Size</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Default Font Size'
@@ -160,10 +162,10 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Family</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item config-item-top'>
<span className='text-gray-700'>Default Font</span>
<span className=''>Default Font</span>
<FontDropdown
options={fontFamilyOptions}
selected={defaultFont}
@@ -173,7 +175,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Override Publisher Font</span>
<span className=''>Override Publisher Font</span>
<input
type='checkbox'
className='toggle'
@@ -187,8 +189,8 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Face</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<FontFace
className='config-item-top'
family='serif'
@@ -3,12 +3,14 @@ import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
import NumberInput from './NumberInput';
import { useTheme } from '@/hooks/useTheme';
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!);
const [fullJustification, setFullJustification] = useState(viewSettings.fullJustification!);
@@ -26,7 +28,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.lineHeight = lineHeight;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [lineHeight]);
useEffect(() => {
@@ -36,7 +38,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.fullJustification = fullJustification;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [fullJustification]);
useEffect(() => {
@@ -46,7 +48,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.hyphenation = hyphenation;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [hyphenation]);
useEffect(() => {
@@ -100,8 +102,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Paragraph</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Line Height'
@@ -112,7 +114,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
step={0.1}
/>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Full Justification</span>
<span className=''>Full Justification</span>
<input
type='checkbox'
className='toggle'
@@ -121,7 +123,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
/>
</div>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Hyphenation</span>
<span className=''>Hyphenation</span>
<input
type='checkbox'
className='toggle'
@@ -135,8 +137,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Page</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Margins (px)'
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useReaderStore } from '@/store/readerStore';
import cssbeautify from 'cssbeautify';
import { getStyles } from '@/utils/style';
import { useTheme } from '@/hooks/useTheme';
const cssRegex =
/((?:\s*)([\w#.@*,:\-.:>+~\[\]\"=(),*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,\/\()\-!#%]+;?)*)*\s*}(?:\s*))/gim;
@@ -10,6 +11,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [animated, setAnimated] = useState(viewSettings.animated!);
const [userStylesheet, setUserStylesheet] = useState(viewSettings.userStylesheet!);
@@ -43,7 +45,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.userStylesheet = formattedCSS;
setSettings(settings);
}
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings));
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
} catch (err) {
setError('Invalid CSS: Please check your input.');
console.log('CSS Error:', err);
@@ -55,14 +55,14 @@ const NumberInput: React.FC<NumberInputProps> = ({
return (
<div className={clsx('config-item', className)}>
<span className='text-gray-700'>{label}</span>
<div className='flex items-center gap-2'>
<span className='text-base-content'>{label}</span>
<div className='text-base-content flex items-center gap-2'>
<input
type='text'
value={localValue}
onChange={handleChange}
onBlur={handleOnBlur}
className='input input-ghost settings-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
className='input input-ghost settings-content text-base-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
onFocus={(e) => e.target.select()}
/>
<button
@@ -25,28 +25,28 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
<div className='dialog-header bg-base-100 sticky top-0 z-10 flex items-center justify-center px-4 pt-2'>
<div className='dialog-tabs flex h-10 max-w-[80%] flex-grow items-center justify-around'>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Font' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Font' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Font')}
>
<RiFontSize size={20} className='mr-0' />
Font
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Layout')}
>
<RiDashboardLine size={20} className='mr-0' />
Layout
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Color')}
>
<VscSymbolColor size={20} className='mr-0' />
Color
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Misc')}
>
<IoAccessibilityOutline size={20} className='mr-0' />
@@ -70,7 +70,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
</div>
</div>
<div className='mt-2 flex-grow overflow-y-auto px-[10%]'>
<div className='text-base-content mt-2 flex-grow overflow-y-auto px-[10%]'>
{activePanel === 'Font' && <FontPanel bookKey={bookKey} />}
{activePanel === 'Layout' && <LayoutPanel bookKey={bookKey} />}
{activePanel === 'Color' && <ColorPanel bookKey={bookKey} />}
@@ -1,4 +1,5 @@
import React from 'react';
import Image from 'next/image';
import { MdInfoOutline } from 'react-icons/md';
import { formatAuthors } from '@/utils/book';
@@ -11,20 +12,22 @@ interface BookCardProps {
const BookCard: React.FC<BookCardProps> = ({ cover, title, author }) => {
return (
<div className='flex h-20 w-full items-center'>
<img
<Image
src={cover}
alt='Book cover'
width={56}
height={80}
className='mr-4 aspect-auto max-h-20 w-[15%] max-w-14 rounded-sm object-cover shadow-md'
/>
<div className='min-w-0 flex-1'>
<h4 className='line-clamp-2 w-[90%] text-sm font-semibold'>{title}</h4>
<p className='truncate text-sm text-gray-600'>{formatAuthors(author)}</p>
<p className='text-neutral-content truncate text-sm'>{formatAuthors(author)}</p>
</div>
<button
className='btn btn-ghost h-6 min-h-6 w-6 rounded-full p-0 transition-colors hover:bg-gray-300'
className='btn btn-ghost hover:bg-base-300 h-6 min-h-6 w-6 rounded-full p-0 transition-colors'
aria-label='More info'
>
<MdInfoOutline size={18} className='text-gray-600' />
<MdInfoOutline size={18} className='fill-base-content' />
</button>
</div>
);
@@ -1,4 +1,6 @@
import React from 'react';
import MenuItem from '@/components/MenuItem';
import useBooksManager from '../../hooks/useBooksManager';
interface BookMenuProps {
@@ -22,29 +24,12 @@ const BookMenu: React.FC<BookMenuProps> = ({ setIsDropdownOpen }) => {
return (
<div
tabIndex={0}
className='book-menu dropdown-content dropdown-center z-20 mt-3 w-56 border bg-white shadow-2xl'
className='book-menu dropdown-content dropdown-center border-base-100 z-20 mt-3 w-56 shadow-2xl'
>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleOpenSplitView}
>
<span className='ml-2'>Parallel Read</span>
<span className='text-sm text-gray-400'>Shift+P</span>
</button>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
onClick={handleReloadPage}
>
<span className='ml-2'>Reload Page</span>
<span className='text-sm text-gray-400'>Shift+R</span>
</button>
<hr className='my-1' />
<button
className='flex w-full items-center rounded-md p-2 hover:bg-gray-100'
onClick={showAboutReadest}
>
<span className='ml-2'>About Readest</span>
</button>
<MenuItem label='Parallel Read' shortcut='Shift+P' onClick={handleOpenSplitView} />
<MenuItem label='Reload Page' shortcut='Shift+R' onClick={handleReloadPage} />
<hr className='border-base-200 my-1' />
<MenuItem label='About Readest' onClick={showAboutReadest} />
</div>
);
};
@@ -51,7 +51,7 @@ const BooknoteItem: React.FC<BooknoteItemProps> = ({ bookKey, item, editable = f
className={clsx(
'border-base-300 my-2 cursor-pointer rounded-lg p-2 text-sm',
editable && 'collapse-arrow collapse',
isCurrent ? 'bg-base-300 hover:bg-gray-300/70' : 'hover:bg-base-300 bg-white',
isCurrent ? 'bg-base-300/85 hover:bg-base-300' : 'hover:bg-base-200 bg-base-100',
)}
tabIndex={0}
onClick={handleClickItem}
@@ -33,22 +33,22 @@ const SidebarHeader: React.FC<{
onClick={onToggleSearchBar}
className={clsx(
'btn btn-ghost left-0 h-8 min-h-8 w-8 p-0',
isSearchBarVisible ? 'bg-gray-300 hover:bg-gray-300' : 'bg-base-200 hover:bg-base-200',
isSearchBarVisible ? 'bg-base-300' : '',
)}
>
<FiSearch size={18} />
<FiSearch size={18} className='text-base-content' />
</button>
<Dropdown
className='dropdown-bottom flex justify-center'
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
toggleButton={<MdOutlineMenu size={20} />}
toggleButton={<MdOutlineMenu size={20} className='fill-base-content' />}
>
<BookMenu />
</Dropdown>
<div className='right-0 flex h-8 w-8 items-center justify-center'>
<button
onClick={onTogglePin}
className={`${isPinned ? 'bg-gray-300' : 'bg-base-300'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
className={`${isPinned ? 'bg-base-300' : 'bg-base-300/65'} btn btn-ghost btn-circle h-6 min-h-6 w-6`}
>
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
</button>
@@ -38,7 +38,7 @@ const SideBar: React.FC<{
settings.globalReadSettings.isSideBarPinned,
);
const onSearchEvent = (event: CustomEvent) => {
const onSearchEvent = async (event: CustomEvent) => {
const { term } = event.detail;
setSideBarVisible(true);
setIsSearchBarVisible(true);
@@ -123,7 +123,7 @@ const SideBar: React.FC<{
onSearchResultChange={setSearchResults}
/>
</div>
<div className='border-b px-3'>
<div className='border-base-300/50 border-b px-3'>
<BookCard cover={book.coverImageUrl!} title={book.title} author={book.author} />
</div>
</div>
@@ -4,6 +4,7 @@ import { TOCItem } from '@/libs/document';
import { useReaderStore } from '@/store/readerStore';
import { findParentPath } from '@/utils/toc';
import { getContentMd5 } from '@/utils/misc';
import clsx from 'clsx';
const createExpanderIcon = (isExpanded: boolean) => {
return (
@@ -11,8 +12,12 @@ const createExpanderIcon = (isExpanded: boolean) => {
viewBox='0 0 8 10'
width='8'
height='10'
className={`transform transition-transform ${isExpanded ? 'rotate-90' : 'rotate-0'}`}
className={clsx(
'text-base-content transform transition-transform',
isExpanded ? 'rotate-90' : 'rotate-0',
)}
style={{ transformOrigin: 'center' }}
fill='currentColor'
>
<polygon points='0 0, 8 5, 0 10' />
</svg>
@@ -59,7 +64,7 @@ const TOCItemView: React.FC<{
aria-selected={isActive ? 'true' : 'false'}
data-href={item.href ? getContentMd5(item.href) : undefined}
className={`flex w-full cursor-pointer items-center rounded-md py-2 ${
isActive ? 'bg-gray-300 hover:bg-gray-400' : 'hover:bg-gray-300'
isActive ? 'bg-base-300/85 hover:bg-base-300' : 'hover:bg-base-300/85'
}`}
>
{item.subitems && (
@@ -5,7 +5,7 @@ const TabNavigation: React.FC<{
activeTab: string;
onTabChange: (tab: string) => void;
}> = ({ activeTab, onTabChange }) => (
<div className='bottom-tab flex w-full border-t'>
<div className='bottom-tab border-base-300/50 flex w-full border-t'>
{['toc', 'annotations', 'bookmarks'].map((tab) => (
<button
key={tab}