forked from akai/readest
Add theme and color config
This commit is contained in:
@@ -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} />}
|
||||
|
||||
Reference in New Issue
Block a user