Archived
Resolve merge conflicts for PR #25: customizable mobile reading experience
- Backend: Replace PR's Book-only models with main's full models (EBook, BookChapter, DownloadRecord, ReadingProgress, ReadingSettings) - Backend: Add reader app (ReadingSettings model, serializer, view, URL) - Frontend: Port reader feature files from web/ to frontend/ (api, hooks, components/reader/, pages/ReadingPage, types/reader, reader.css) - Frontend: Add /read/:id route to App.tsx for chapter-based reading view - All imports adjusted for frontend/src directory structure
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* useReadingSettings — fetch and manage user reading preferences.
|
||||
* Applies settings as CSS custom properties on the document root.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
getReadingSettings,
|
||||
updateReadingSettings,
|
||||
} from "../api/reader";
|
||||
import type { ReadingSettings } from "../types/reader";
|
||||
|
||||
const DEFAULT_SETTINGS: ReadingSettings = {
|
||||
font_family: "serif",
|
||||
font_size: 18,
|
||||
line_height: 1.6,
|
||||
margin_width: 16,
|
||||
background_color: "#f5f0eb",
|
||||
text_color: "#1a1a1a",
|
||||
brightness: 100,
|
||||
orientation_lock: "auto",
|
||||
theme: "sepia",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
};
|
||||
|
||||
function applyCssVariables(settings: ReadingSettings): void {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty("--reader-bg", settings.background_color);
|
||||
root.style.setProperty("--reader-text", settings.text_color);
|
||||
root.style.setProperty("--reader-font-family", settings.font_family);
|
||||
root.style.setProperty("--reader-font-size", `${settings.font_size}px`);
|
||||
root.style.setProperty("--reader-line-height", String(settings.line_height));
|
||||
root.style.setProperty("--reader-margin", `${settings.margin_width}px`);
|
||||
root.style.setProperty("--reader-brightness", `${settings.brightness}%`);
|
||||
}
|
||||
|
||||
export interface UseReadingSettingsReturn {
|
||||
settings: ReadingSettings;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
updateSettings: (partial: Partial<ReadingSettings>) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useReadingSettings(): UseReadingSettingsReturn {
|
||||
const [settings, setSettings] = useState<ReadingSettings>(DEFAULT_SETTINGS);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Fetch settings on mount
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
getReadingSettings()
|
||||
.then((data) => {
|
||||
if (!cancelled) {
|
||||
setSettings(data);
|
||||
applyCssVariables(data);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!cancelled) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to load reading settings"
|
||||
);
|
||||
// Apply defaults
|
||||
applyCssVariables(DEFAULT_SETTINGS);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateSettings = useCallback(
|
||||
async (partial: Partial<ReadingSettings>) => {
|
||||
try {
|
||||
const updated = await updateReadingSettings(partial);
|
||||
setSettings(updated);
|
||||
applyCssVariables(updated);
|
||||
setError(null);
|
||||
} catch (err: unknown) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to update reading settings"
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return { settings, isLoading, error, updateSettings };
|
||||
}
|
||||
Reference in New Issue
Block a user