forked from akai/readest
feat: add support for per-book background image settings in addition to global settings, also closes #2223 (#2225)
This commit is contained in:
@@ -15,6 +15,7 @@ import { usePagination } from '../hooks/usePagination';
|
|||||||
import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
||||||
import { useProgressSync } from '../hooks/useProgressSync';
|
import { useProgressSync } from '../hooks/useProgressSync';
|
||||||
import { useProgressAutoSave } from '../hooks/useProgressAutoSave';
|
import { useProgressAutoSave } from '../hooks/useProgressAutoSave';
|
||||||
|
import { useBackgroundTexture } from '@/hooks/useBackgroundTexture';
|
||||||
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
import { useAutoFocus } from '@/hooks/useAutoFocus';
|
||||||
import { useTranslation } from '@/hooks/useTranslation';
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import { useKOSync } from '../hooks/useKOSync';
|
import { useKOSync } from '../hooks/useKOSync';
|
||||||
@@ -73,6 +74,7 @@ const FoliateViewer: React.FC<{
|
|||||||
const { getViewState, getViewSettings, setViewSettings } = useReaderStore();
|
const { getViewState, getViewSettings, setViewSettings } = useReaderStore();
|
||||||
const { getParallels } = useParallelViewStore();
|
const { getParallels } = useParallelViewStore();
|
||||||
const { getBookData } = useBookDataStore();
|
const { getBookData } = useBookDataStore();
|
||||||
|
const { applyBackgroundTexture } = useBackgroundTexture();
|
||||||
const viewState = getViewState(bookKey);
|
const viewState = getViewState(bookKey);
|
||||||
const viewSettings = getViewSettings(bookKey);
|
const viewSettings = getViewSettings(bookKey);
|
||||||
|
|
||||||
@@ -145,14 +147,19 @@ const FoliateViewer: React.FC<{
|
|||||||
const writingDir = viewRef.current?.renderer.setStyles && getDirection(detail.doc);
|
const writingDir = viewRef.current?.renderer.setStyles && getDirection(detail.doc);
|
||||||
const viewSettings = getViewSettings(bookKey)!;
|
const viewSettings = getViewSettings(bookKey)!;
|
||||||
const bookData = getBookData(bookKey)!;
|
const bookData = getBookData(bookKey)!;
|
||||||
viewSettings.vertical =
|
|
||||||
|
const newVertical =
|
||||||
writingDir?.vertical || viewSettings.writingMode.includes('vertical') || false;
|
writingDir?.vertical || viewSettings.writingMode.includes('vertical') || false;
|
||||||
viewSettings.rtl =
|
const newRtl =
|
||||||
writingDir?.rtl ||
|
writingDir?.rtl ||
|
||||||
getDirFromUILanguage() === 'rtl' ||
|
getDirFromUILanguage() === 'rtl' ||
|
||||||
viewSettings.writingMode.includes('rl') ||
|
viewSettings.writingMode.includes('rl') ||
|
||||||
false;
|
false;
|
||||||
setViewSettings(bookKey, { ...viewSettings });
|
if (viewSettings.vertical !== newVertical || viewSettings.rtl !== newRtl) {
|
||||||
|
viewSettings.vertical = newVertical;
|
||||||
|
viewSettings.rtl = newRtl;
|
||||||
|
setViewSettings(bookKey, { ...viewSettings });
|
||||||
|
}
|
||||||
|
|
||||||
mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage));
|
mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage));
|
||||||
|
|
||||||
@@ -395,6 +402,16 @@ const FoliateViewer: React.FC<{
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [settings.customFonts, envConfig]);
|
}, [settings.customFonts, envConfig]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!viewSettings) return;
|
||||||
|
applyBackgroundTexture(envConfig, viewSettings);
|
||||||
|
}, [
|
||||||
|
viewSettings?.backgroundTextureId,
|
||||||
|
viewSettings?.backgroundOpacity,
|
||||||
|
viewSettings?.backgroundSize,
|
||||||
|
applyBackgroundTexture,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewRef.current && viewRef.current.renderer) {
|
if (viewRef.current && viewRef.current.renderer) {
|
||||||
doubleClickDisabled.current = !!viewSettings?.disableDoubleClick;
|
doubleClickDisabled.current = !!viewSettings?.disableDoubleClick;
|
||||||
|
|||||||
@@ -142,7 +142,11 @@ export const useProgressSync = (bookKey: string) => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
Object.entries(syncedConfig).filter(([_, value]) => value !== null && value !== undefined),
|
Object.entries(syncedConfig).filter(([_, value]) => value !== null && value !== undefined),
|
||||||
);
|
);
|
||||||
setConfig(bookKey, { ...config, ...filteredSyncedConfig });
|
if (syncedConfig.updatedAt >= config.updatedAt) {
|
||||||
|
setConfig(bookKey, { ...config, ...filteredSyncedConfig });
|
||||||
|
} else {
|
||||||
|
setConfig(bookKey, { ...filteredSyncedConfig, ...config });
|
||||||
|
}
|
||||||
if (remoteCFILocation && configCFI) {
|
if (remoteCFILocation && configCFI) {
|
||||||
if (CFI.compare(configCFI, remoteCFILocation) < 0) {
|
if (CFI.compare(configCFI, remoteCFILocation) < 0) {
|
||||||
if (view) {
|
if (view) {
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { useSettingsStore } from '@/store/settingsStore';
|
|||||||
import { useDeviceControlStore } from '@/store/deviceStore';
|
import { useDeviceControlStore } from '@/store/deviceStore';
|
||||||
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
||||||
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
|
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
|
||||||
import { useCustomTextureStore } from '@/store/customTextureStore';
|
import { useBackgroundTexture } from '@/hooks/useBackgroundTexture';
|
||||||
import { getLocale } from '@/utils/misc';
|
import { getLocale } from '@/utils/misc';
|
||||||
|
|
||||||
const Providers = ({ children }: { children: React.ReactNode }) => {
|
const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||||
const { envConfig, appService } = useEnv();
|
const { envConfig, appService } = useEnv();
|
||||||
const { applyUILanguage } = useSettingsStore();
|
const { applyUILanguage } = useSettingsStore();
|
||||||
const { setScreenBrightness } = useDeviceControlStore();
|
const { setScreenBrightness } = useDeviceControlStore();
|
||||||
const { addTexture, applyTexture } = useCustomTextureStore();
|
const { applyBackgroundTexture } = useBackgroundTexture();
|
||||||
const iconSize = useDefaultIconSize();
|
const iconSize = useDefaultIconSize();
|
||||||
useSafeAreaInsets(); // Initialize safe area insets
|
useSafeAreaInsets(); // Initialize safe area insets
|
||||||
|
|
||||||
@@ -47,21 +47,10 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (appService.hasScreenBrightness && brightness >= 0) {
|
if (appService.hasScreenBrightness && brightness >= 0) {
|
||||||
setScreenBrightness(brightness / 100);
|
setScreenBrightness(brightness / 100);
|
||||||
}
|
}
|
||||||
const textureId = globalViewSettings.backgroundTextureId;
|
applyBackgroundTexture(envConfig, globalViewSettings);
|
||||||
const textureOpacity = globalViewSettings.backgroundOpacity;
|
|
||||||
const textureSize = globalViewSettings.backgroundSize;
|
|
||||||
if (textureId && textureId !== 'none') {
|
|
||||||
document.documentElement.style.setProperty('--bg-texture-opacity', `${textureOpacity}`);
|
|
||||||
document.documentElement.style.setProperty('--bg-texture-size', textureSize);
|
|
||||||
const customTexture = settings.customTextures?.find((t) => t.id === textureId);
|
|
||||||
if (customTexture) {
|
|
||||||
addTexture(customTexture.path);
|
|
||||||
}
|
|
||||||
applyTexture(envConfig, textureId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [envConfig, appService, applyUILanguage, addTexture, applyTexture, setScreenBrightness]);
|
}, [envConfig, appService, applyUILanguage, setScreenBrightness, applyBackgroundTexture]);
|
||||||
|
|
||||||
// Make sure appService is available in all children components
|
// Make sure appService is available in all children components
|
||||||
if (!appService) return;
|
if (!appService) return;
|
||||||
|
|||||||
@@ -142,10 +142,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
|||||||
|
|
||||||
const applyBackgroundTexture = () => {
|
const applyBackgroundTexture = () => {
|
||||||
applyTexture(envConfig, selectedTextureId);
|
applyTexture(envConfig, selectedTextureId);
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty('--bg-texture-opacity', `${backgroundOpacity}`);
|
||||||
'--bg-texture-opacity',
|
|
||||||
backgroundOpacity.toString(),
|
|
||||||
);
|
|
||||||
document.documentElement.style.setProperty('--bg-texture-size', backgroundSize);
|
document.documentElement.style.setProperty('--bg-texture-size', backgroundSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useCustomTextureStore } from '@/store/customTextureStore';
|
||||||
|
import { useSettingsStore } from '@/store/settingsStore';
|
||||||
|
import { EnvConfigType } from '@/services/environment';
|
||||||
|
import { ViewSettings } from '@/types/book';
|
||||||
|
|
||||||
|
export const useBackgroundTexture = () => {
|
||||||
|
const applyBackgroundTexture = useCallback(
|
||||||
|
(envConfig: EnvConfigType, viewSettings: ViewSettings) => {
|
||||||
|
const textureId = viewSettings.backgroundTextureId;
|
||||||
|
const textureOpacity = viewSettings.backgroundOpacity;
|
||||||
|
const textureSize = viewSettings.backgroundSize;
|
||||||
|
if (!textureId || textureId === 'none') return;
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--bg-texture-opacity', `${textureOpacity}`);
|
||||||
|
document.documentElement.style.setProperty('--bg-texture-size', textureSize);
|
||||||
|
|
||||||
|
const settings = useSettingsStore.getState().settings;
|
||||||
|
const customTexture = settings.customTextures?.find((t) => t.id === textureId);
|
||||||
|
|
||||||
|
if (customTexture) {
|
||||||
|
useCustomTextureStore.getState().addTexture(customTexture.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
useCustomTextureStore.getState().applyTexture(envConfig, textureId);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { applyBackgroundTexture };
|
||||||
|
};
|
||||||
@@ -155,7 +155,7 @@ export const DEFAULT_BOOK_STYLE: BookStyle = {
|
|||||||
overrideColor: false,
|
overrideColor: false,
|
||||||
backgroundTextureId: 'none',
|
backgroundTextureId: 'none',
|
||||||
backgroundOpacity: 0.6,
|
backgroundOpacity: 0.6,
|
||||||
backgroundSize: 'auto',
|
backgroundSize: 'cover',
|
||||||
codeHighlighting: false,
|
codeHighlighting: false,
|
||||||
codeLanguage: 'auto-detect',
|
codeLanguage: 'auto-detect',
|
||||||
userStylesheet: '',
|
userStylesheet: '',
|
||||||
|
|||||||
@@ -288,10 +288,9 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
|
|||||||
const oldConfig = bookData.config;
|
const oldConfig = bookData.config;
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
...bookData.config,
|
...bookData.config,
|
||||||
updatedAt: Date.now(),
|
|
||||||
progress,
|
progress,
|
||||||
location,
|
location,
|
||||||
};
|
} as BookConfig;
|
||||||
|
|
||||||
useBookDataStore.setState((state) => ({
|
useBookDataStore.setState((state) => ({
|
||||||
booksData: {
|
booksData: {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const createTextureCSS = (texture: BackgroundTexture) => {
|
|||||||
body::before, .sidebar-container::before, .notebook-container::before,
|
body::before, .sidebar-container::before, .notebook-container::before,
|
||||||
.foliate-viewer::before {
|
.foliate-viewer::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -68,10 +68,14 @@ const createTextureCSS = (texture: BackgroundTexture) => {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
background-image: url("${texture.blobUrl || texture.url}");
|
background-image: url("${texture.blobUrl || texture.url}");
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: var(--bg-texture-size, auto);
|
background-size: var(--bg-texture-size, cover);
|
||||||
mix-blend-mode: var(--bg-texture-blend-mode, normal);
|
mix-blend-mode: var(--bg-texture-blend-mode, multiply);
|
||||||
opacity: var(--bg-texture-opacity, 0.6);
|
opacity: var(--bg-texture-opacity, 0.6);
|
||||||
}`;
|
}
|
||||||
|
body::before {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
return css;
|
return css;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user