Added view settings hirarchy of global settings < book settings < view settings

This commit is contained in:
chrox
2024-11-09 14:56:24 +01:00
parent d5835780a0
commit a9fd1a00e6
9 changed files with 144 additions and 96 deletions
@@ -18,7 +18,7 @@ interface BookGridProps {
const BookGrid: React.FC<BookGridProps> = ({ bookKeys, onCloseBook }) => {
const { isSideBarPinned, isSideBarVisible, sideBarWidth } = useReaderStore();
const { getConfig, getProgress, getBookData, getViewState } = useReaderStore();
const { getConfig, getProgress, getBookData, getViewState, getViewSettings } = useReaderStore();
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useReaderStore();
const gridWidth = isSideBarPinned && isSideBarVisible ? `calc(100% - ${sideBarWidth})` : '100%';
const gridTemplate = getGridTemplate(bookKeys.length, window.innerWidth / window.innerHeight);
@@ -36,12 +36,13 @@ const BookGrid: React.FC<BookGridProps> = ({ bookKeys, onCloseBook }) => {
const bookData = getBookData(bookKey);
const config = getConfig(bookKey);
const progress = getProgress(bookKey);
const viewSettings = getViewSettings(bookKey);
const { book, bookDoc } = bookData || {};
if (!book || !config || !bookDoc) return null;
if (!book || !config || !bookDoc || !viewSettings) return null;
const isBookmarked = getViewState(bookKey)?.ribbonVisible;
const { section, pageinfo, tocLabel: chapter } = progress || {};
const marginGap = `${config.viewSettings!.gapPercent}%`;
const isBookmarked = getViewState(bookKey)?.ribbonVisible;
const marginGap = `${viewSettings.gapPercent}%`;
return (
<div
@@ -58,7 +59,7 @@ const BookGrid: React.FC<BookGridProps> = ({ bookKeys, onCloseBook }) => {
onSetSettingsDialogOpen={setFontLayoutSettingsDialogOpen}
/>
<FoliateViewer bookKey={bookKey} bookDoc={bookDoc} config={config} />
{config.viewSettings!.scrolled ? null : (
{viewSettings.scrolled ? null : (
<>
<SectionInfo chapter={chapter} gapLeft={marginGap} />
<PageInfoView
@@ -49,8 +49,7 @@ const FoliateViewer: React.FC<{
const [view, setView] = useState<FoliateView | null>(null);
const [viewInited, setViewInited] = useState(false);
const isViewCreated = useRef(false);
const { setView: setFoliateView } = useReaderStore();
const setProgress = useReaderStore((state) => state.setProgress);
const { setView: setFoliateView, setProgress, getViewSettings } = useReaderStore();
const progressRelocateHandler = (event: Event) => {
const detail = (event as CustomEvent).detail;
@@ -118,8 +117,8 @@ const FoliateViewer: React.FC<{
setFoliateView(bookKey, view);
await view.open(bookDoc);
const viewSettings = config.viewSettings!;
view.renderer.setStyles?.(getStyles(config));
const viewSettings = getViewSettings(bookKey)!;
view.renderer.setStyles?.(getStyles(viewSettings));
const isScrolled = viewSettings.scrolled!;
const marginPx = viewSettings.marginPx!;
const gapPercent = viewSettings.gapPercent!;
@@ -18,13 +18,13 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
setIsDropdownOpen,
onSetSettingsDialogOpen,
}) => {
const { getConfig, setConfig, getView } = useReaderStore();
const config = getConfig(bookKey)!;
const { getView, getViewSettings, setViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey);
const [isDarkMode, setDarkMode] = useState(config.viewSettings!.theme === 'dark');
const [isScrolledMode, setScrolledMode] = useState(config.viewSettings!.scrolled);
const [isInvertedColors, setInvertedColors] = useState(config.viewSettings!.invert);
const [zoomLevel, setZoomLevel] = useState(config.viewSettings!.zoomLevel!);
const [isDarkMode, setDarkMode] = useState(viewSettings!.theme === 'dark');
const [isScrolledMode, setScrolledMode] = useState(viewSettings!.scrolled);
const [isInvertedColors, setInvertedColors] = useState(viewSettings!.invert);
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
const zoomIn = () => setZoomLevel((prev) => Math.min(prev + 10, 200));
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - 10, 50));
@@ -40,8 +40,8 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
useEffect(() => {
getView(bookKey)?.renderer.setAttribute('flow', isScrolledMode ? 'scrolled' : 'paginated');
config.viewSettings!.scrolled = isScrolledMode;
setConfig(bookKey, config);
viewSettings!.scrolled = isScrolledMode;
setViewSettings(bookKey, viewSettings!);
}, [isScrolledMode]);
useEffect(() => {
@@ -52,11 +52,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
const view = getView(bookKey);
if (!view) return;
// FIXME: zoom level is not working in paginated mode
if (config.viewSettings?.scrolled) {
view.renderer.setStyles?.(getStyles(config));
if (viewSettings?.scrolled) {
view.renderer.setStyles?.(getStyles(viewSettings!));
}
config.viewSettings!.zoomLevel = zoomLevel;
setConfig(bookKey, config);
viewSettings!.zoomLevel = zoomLevel;
setViewSettings(bookKey, viewSettings!);
}, [zoomLevel]);
return (
@@ -37,86 +37,86 @@ const FontFace = ({ className, family, label, options, selected, onSelect }: Fon
const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getConfig, setConfig, getView } = useReaderStore();
const config = getConfig(bookKey)!;
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const [defaultFontSize, setDefaultFontSize] = useState(config.viewSettings!.defaultFontSize!);
const [minFontSize, setMinFontSize] = useState(config.viewSettings!.minimumFontSize!);
const [overrideFont, setOverrideFont] = useState(config.viewSettings!.overrideFont!);
const [defaultFont, setDefaultFont] = useState(config.viewSettings!.defaultFont!);
const [serifFont, setSerifFont] = useState(config.viewSettings!.serifFont!);
const [sansSerifFont, setSansSerifFont] = useState(config.viewSettings!.sansSerifFont!);
const [monospaceFont, setMonospaceFont] = useState(config.viewSettings!.monospaceFont!);
const [defaultFontSize, setDefaultFontSize] = useState(viewSettings.defaultFontSize!);
const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!);
const [overrideFont, setOverrideFont] = useState(viewSettings.overrideFont!);
const [defaultFont, setDefaultFont] = useState(viewSettings.defaultFont!);
const [serifFont, setSerifFont] = useState(viewSettings.serifFont!);
const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!);
const [monospaceFont, setMonospaceFont] = useState(viewSettings.monospaceFont!);
useEffect(() => {
config.viewSettings!.defaultFont = defaultFont;
setConfig(bookKey, config);
viewSettings.defaultFont = defaultFont;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.defaultFont = defaultFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [defaultFont]);
useEffect(() => {
config.viewSettings!.defaultFontSize = defaultFontSize;
setConfig(bookKey, config);
viewSettings.defaultFontSize = defaultFontSize;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.defaultFontSize = defaultFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [defaultFontSize]);
useEffect(() => {
config.viewSettings!.minimumFontSize = minFontSize;
setConfig(bookKey, config);
viewSettings.minimumFontSize = minFontSize;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.minimumFontSize = minFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [minFontSize]);
useEffect(() => {
config.viewSettings!.serifFont = serifFont;
setConfig(bookKey, config);
viewSettings.serifFont = serifFont;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.serifFont = serifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [serifFont]);
useEffect(() => {
config.viewSettings!.sansSerifFont = sansSerifFont;
setConfig(bookKey, config);
viewSettings.sansSerifFont = sansSerifFont;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.sansSerifFont = sansSerifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [sansSerifFont]);
useEffect(() => {
config.viewSettings!.monospaceFont = monospaceFont;
setConfig(bookKey, config);
viewSettings.monospaceFont = monospaceFont;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.monospaceFont = monospaceFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [monospaceFont]);
useEffect(() => {
config.viewSettings!.overrideFont = overrideFont;
setConfig(bookKey, config);
viewSettings.overrideFont = overrideFont;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.overrideFont = overrideFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [overrideFont]);
const handleFontFamilyFont = (option: string) => {
@@ -6,54 +6,52 @@ import NumberInput from './NumberInput';
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getConfig, setConfig, getView } = useReaderStore();
const config = getConfig(bookKey)!;
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const [lineHeight, setLineHeight] = useState(config.viewSettings!.lineHeight!);
const [fullJustification, setFullJustification] = useState(
config.viewSettings!.fullJustification!,
);
const [hyphenation, setHyphenation] = useState(config.viewSettings!.hyphenation!);
const [marginPx, setMarginPx] = useState(config.viewSettings!.marginPx!);
const [gapPercent, setGapPercent] = useState(config.viewSettings!.gapPercent!);
const [maxColumnCount, setMaxColumnCount] = useState(config.viewSettings!.maxColumnCount!);
const [maxInlineSize, setMaxInlineSize] = useState(config.viewSettings!.maxInlineSize!);
const [maxBlockSize, setMaxBlockSize] = useState(config.viewSettings!.maxBlockSize!);
const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!);
const [fullJustification, setFullJustification] = useState(viewSettings.fullJustification!);
const [hyphenation, setHyphenation] = useState(viewSettings.hyphenation!);
const [marginPx, setMarginPx] = useState(viewSettings.marginPx!);
const [gapPercent, setGapPercent] = useState(viewSettings.gapPercent!);
const [maxColumnCount, setMaxColumnCount] = useState(viewSettings.maxColumnCount!);
const [maxInlineSize, setMaxInlineSize] = useState(viewSettings.maxInlineSize!);
const [maxBlockSize, setMaxBlockSize] = useState(viewSettings.maxBlockSize!);
useEffect(() => {
config.viewSettings!.lineHeight = lineHeight;
setConfig(bookKey, config);
viewSettings.lineHeight = lineHeight;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.lineHeight = lineHeight;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [lineHeight]);
useEffect(() => {
config.viewSettings!.fullJustification = fullJustification;
setConfig(bookKey, config);
viewSettings.fullJustification = fullJustification;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.fullJustification = fullJustification;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [fullJustification]);
useEffect(() => {
config.viewSettings!.hyphenation = hyphenation;
setConfig(bookKey, config);
viewSettings.hyphenation = hyphenation;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.hyphenation = hyphenation;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(config));
view?.renderer.setStyles?.(getStyles(viewSettings));
}, [hyphenation]);
useEffect(() => {
config.viewSettings!.marginPx = marginPx;
setConfig(bookKey, config);
viewSettings.marginPx = marginPx;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.marginPx = marginPx;
setSettings(settings);
@@ -62,8 +60,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
}, [marginPx]);
useEffect(() => {
config.viewSettings!.gapPercent = gapPercent;
setConfig(bookKey, config);
viewSettings.gapPercent = gapPercent;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.gapPercent = gapPercent;
setSettings(settings);
@@ -72,8 +70,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
}, [gapPercent]);
useEffect(() => {
config.viewSettings!.maxColumnCount = maxColumnCount;
setConfig(bookKey, config);
viewSettings.maxColumnCount = maxColumnCount;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.maxColumnCount = maxColumnCount;
setSettings(settings);
@@ -86,8 +84,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
}, [maxColumnCount]);
useEffect(() => {
config.viewSettings!.maxInlineSize = maxInlineSize;
setConfig(bookKey, config);
viewSettings.maxInlineSize = maxInlineSize;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.maxInlineSize = maxInlineSize;
setSettings(settings);
@@ -8,11 +8,11 @@ const cssRegex =
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getConfig, setConfig, getView } = useReaderStore();
const config = getConfig(bookKey)!;
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const [animated, setAnimated] = useState(config.viewSettings!.animated!);
const [userStylesheet, setUserStylesheet] = useState(config.viewSettings!.userStylesheet!);
const [animated, setAnimated] = useState(viewSettings.animated!);
const [userStylesheet, setUserStylesheet] = useState(viewSettings.userStylesheet!);
const [error, setError] = useState<string | null>(null);
let cssInput = userStylesheet;
@@ -37,13 +37,13 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
}
setError(null);
config.viewSettings!.userStylesheet = formattedCSS;
setConfig(bookKey, config);
viewSettings.userStylesheet = formattedCSS;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.userStylesheet = formattedCSS;
setSettings(settings);
}
getView(bookKey)?.renderer.setStyles?.(getStyles(config));
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings));
} catch (err) {
setError('Invalid CSS: Please check your input.');
console.log('CSS Error:', err);
@@ -56,8 +56,8 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
};
useEffect(() => {
config.viewSettings!.animated = animated;
setConfig(bookKey, config);
viewSettings.animated = animated;
setViewSettings(bookKey, viewSettings);
if (isFontLayoutSettingsGlobal) {
settings.globalViewSettings.animated = animated;
setSettings(settings);
@@ -14,9 +14,8 @@ const useBookShortcuts = ({
openSplitView,
getNextBookKey,
}: UseBookShortcutsProps) => {
const { getView, getConfig, toggleSideBar, setSideBarBookKey } = useReaderStore();
const config = getConfig(sideBarBookKey);
const viewSettings = config?.viewSettings;
const { getView, toggleSideBar, setSideBarBookKey, getViewSettings } = useReaderStore();
const viewSettings = getViewSettings(sideBarBookKey ?? '');
const fontSize = viewSettings?.defaultFontSize ?? 16;
const lineHeight = viewSettings?.lineHeight ?? 1.6;
const distance = fontSize * lineHeight * 3;
+53 -1
View File
@@ -1,6 +1,14 @@
import { create } from 'zustand';
import { BookNote, BookContent, Book, BookConfig, PageInfo, BookProgress } from '@/types/book';
import {
BookNote,
BookContent,
Book,
BookConfig,
PageInfo,
BookProgress,
ViewSettings,
} from '@/types/book';
import { EnvConfigType } from '@/services/environment';
import { SystemSettings } from '@/types/settings';
import { FoliateView } from '@/app/reader/components/FoliateViewer';
@@ -25,6 +33,11 @@ export interface ViewState {
error: string | null;
progress: BookProgress | null;
ribbonVisible: boolean;
/* View settings for the view:
generally view settings have a hirarchy of global settings < book settings < view settings
view settings for primary view are saved to book config which is persisted to config file
ommitting settings that are not changed from global settings */
viewSettings: ViewSettings | null;
}
interface ReaderStore {
@@ -71,6 +84,8 @@ interface ReaderStore {
setView: (key: string, view: FoliateView) => void;
getView: (key: string | null) => FoliateView | null;
getViewsById: (id: string) => FoliateView[];
setViewSettings: (key: string, viewSettings: ViewSettings) => void;
getViewSettings: (key: string) => ViewSettings | null;
deleteBook: (envConfig: EnvConfigType, book: Book) => void;
saveConfig: (
@@ -208,6 +223,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
error: null,
progress: null,
ribbonVisible: false,
viewSettings: null,
},
},
}));
@@ -232,6 +248,9 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
},
}));
}
const config = get().booksData[id]?.config as BookConfig;
const configViewSettings = config.viewSettings!;
console.log('Initializing book view settings', configViewSettings);
set((state) => ({
viewStates: {
...state.viewStates,
@@ -244,6 +263,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
error: null,
progress: null,
ribbonVisible: false,
viewSettings: JSON.parse(JSON.stringify(configViewSettings)) as ViewSettings,
},
},
}));
@@ -261,6 +281,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
error: 'Failed to load book.',
progress: null,
ribbonVisible: false,
viewSettings: null,
},
},
}));
@@ -271,6 +292,37 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
return get().booksData[id] || null;
},
getViewState: (key: string) => get().viewStates[key] || null,
setViewSettings: (key: string, viewSettings: ViewSettings) => {
const id = key.split('-')[0]!;
const bookData = get().booksData[id];
const viewState = get().viewStates[key];
if (!viewState || !bookData) return;
if (viewState.isPrimary) {
set((state) => ({
booksData: {
...state.booksData,
[id]: {
...bookData,
config: {
...bookData.config,
lastUpdated: Date.now(),
viewSettings,
},
},
},
}));
}
set((state) => ({
viewStates: {
...state.viewStates,
[key]: {
...state.viewStates[key]!,
viewSettings,
},
},
}));
},
getViewSettings: (key: string) => get().viewStates[key]?.viewSettings || null,
setProgress: (
key: string,
location: string,
+2 -3
View File
@@ -1,5 +1,5 @@
import { MONOSPACE_FONTS, SANS_SERIF_FONTS, SERIF_FONTS } from '@/services/constants';
import { BookConfig } from '@/types/book';
import { ViewSettings } from '@/types/book';
import fontfacesCSS from '!!raw-loader!../styles/fonts.css';
@@ -76,8 +76,7 @@ const getLayoutStyles = (
}
`;
export const getStyles = (config: BookConfig) => {
const viewSettings = config.viewSettings!;
export const getStyles = (viewSettings: ViewSettings) => {
const layoutStyles = getLayoutStyles(
viewSettings.lineHeight!,
viewSettings.fullJustification!,