Archived
- Backend: Chapter, ReadingProgress, ReadingSettings models - Backend: Chapter API (TOC + content), progress tracking, settings CRUD - Frontend: ReadingPage with chapter navigation - Frontend: TableOfContents drawer - Frontend: ReadingSettingsPanel (theme, font, size, orientation) - Frontend: Custom hooks for settings, chapters, progress tracking - CSS: Mobile-first reading view with sepia/dark/light/paper themes - Route: /reader/:bookId reading view from book detail page - Docs: 001-customizable-mobile-reading-experience.md
96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
/**
|
|
* 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 };
|
|
} |