Merge pull request 'refactor: improve reading progress synchronization logic and add implementation guidelines' (#36) from feat/progress-track into main
Reviewed-on: #36
This commit is contained in:
commit
fe7d5957e3
@ -15,19 +15,14 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
|
||||
if (jsonString) {
|
||||
try {
|
||||
const data: ReadingTrackerData = JSON.parse(jsonString);
|
||||
return data.chapterPage[chapterId] || 1;
|
||||
return data.chapterPage[chapterId] ?? null;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse local progress", e);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
return null;
|
||||
});
|
||||
|
||||
const [serverProgress, setServerProgress] = useState<{
|
||||
pageNumber: number;
|
||||
updatedAt: string;
|
||||
} | null>(null);
|
||||
|
||||
const lastSyncedPage = useRef<number | null>(null);
|
||||
const syncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
@ -38,36 +33,36 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
|
||||
);
|
||||
const { mutate: updateProgress } = useUpdateProgress();
|
||||
|
||||
const serverProgress =
|
||||
progressData &&
|
||||
progressData.pageNumber !== undefined &&
|
||||
progressData.updatedAt !== undefined
|
||||
? {
|
||||
pageNumber: progressData.pageNumber,
|
||||
updatedAt: progressData.updatedAt,
|
||||
}
|
||||
: null;
|
||||
|
||||
// Sync local progress when chapterId changes
|
||||
useEffect(() => {
|
||||
const jsonString = localStorage.getItem("readingTrackerData");
|
||||
if (jsonString) {
|
||||
try {
|
||||
const data: ReadingTrackerData = JSON.parse(jsonString);
|
||||
const localPage = data.chapterPage[chapterId] || 1;
|
||||
const localPage = data.chapterPage[chapterId] ?? null;
|
||||
setCurrentPage(localPage);
|
||||
lastSyncedPage.current = localPage;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
setCurrentPage(1);
|
||||
lastSyncedPage.current = 1;
|
||||
setCurrentPage(null);
|
||||
lastSyncedPage.current = null;
|
||||
}
|
||||
}, [chapterId]);
|
||||
|
||||
// Sync server progress when available
|
||||
useEffect(() => {
|
||||
if (progressData && progressData.pageNumber !== undefined && progressData.updatedAt !== undefined) {
|
||||
setServerProgress({
|
||||
pageNumber: progressData.pageNumber,
|
||||
updatedAt: progressData.updatedAt,
|
||||
});
|
||||
}
|
||||
}, [progressData]);
|
||||
|
||||
const saveLocalProgress = useCallback(
|
||||
(page: number) => {
|
||||
(page: number, forceSync = false) => {
|
||||
setCurrentPage(page);
|
||||
const jsonString = localStorage.getItem("readingTrackerData");
|
||||
let data: ReadingTrackerData = { chapterPage: {}, updatedAt: {} };
|
||||
@ -85,8 +80,9 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
|
||||
|
||||
// Debounced backend sync
|
||||
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
|
||||
syncTimerRef.current = setTimeout(() => {
|
||||
if (page !== lastSyncedPage.current) {
|
||||
|
||||
const performSync = () => {
|
||||
if (forceSync || page !== lastSyncedPage.current) {
|
||||
updateProgress({
|
||||
data: {
|
||||
mangaId,
|
||||
@ -96,11 +92,23 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
|
||||
});
|
||||
lastSyncedPage.current = page;
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
if (forceSync) {
|
||||
performSync();
|
||||
} else {
|
||||
syncTimerRef.current = setTimeout(performSync, 5000);
|
||||
}
|
||||
},
|
||||
[mangaId, chapterId, updateProgress],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const applyServerProgress = useCallback(() => {
|
||||
if (serverProgress) {
|
||||
saveLocalProgress(serverProgress.pageNumber);
|
||||
|
||||
@ -53,15 +53,33 @@ const Chapter = () => {
|
||||
const localPage = savedPage;
|
||||
const serverPage = serverProgress?.pageNumber;
|
||||
|
||||
if (!localPage && !serverPage) {
|
||||
if (localPage === null) {
|
||||
// No local history for this chapter, use server or default to 1
|
||||
const targetPage = serverPage || 1;
|
||||
setCurrentPage(targetPage);
|
||||
setVisibleCount(targetPage);
|
||||
if (targetPage > 1) {
|
||||
setIsAutoScrolling(true);
|
||||
}
|
||||
setHasInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (localPage && serverPage && localPage !== serverPage) {
|
||||
setShowConflictDialog(true);
|
||||
if (serverPage && localPage !== serverPage) {
|
||||
// If local is just starting out (page 1), just take server
|
||||
if (localPage <= 1) {
|
||||
const targetPage = serverPage;
|
||||
setCurrentPage(targetPage);
|
||||
setVisibleCount(targetPage);
|
||||
if (targetPage > 1) {
|
||||
setIsAutoScrolling(true);
|
||||
}
|
||||
setHasInitialized(true);
|
||||
} else {
|
||||
setShowConflictDialog(true);
|
||||
}
|
||||
} else {
|
||||
const targetPage = localPage || serverPage || 1;
|
||||
const targetPage = localPage || 1;
|
||||
setCurrentPage(targetPage);
|
||||
setVisibleCount(targetPage);
|
||||
if (targetPage > 1) {
|
||||
@ -181,7 +199,7 @@ const Chapter = () => {
|
||||
}
|
||||
}, [infiniteScroll]);
|
||||
|
||||
if (isLoading || !hasInitialized) {
|
||||
if (isLoading || (isLoadingProgress && !hasInitialized)) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background">
|
||||
<MangaLoadingState />
|
||||
@ -245,7 +263,7 @@ const Chapter = () => {
|
||||
<Dialog open={showConflictDialog} onOpenChange={setShowConflictDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Progress Conflict</DialogTitle>
|
||||
<DialogTitle>Reading Progress Conflict</DialogTitle>
|
||||
<DialogDescription>
|
||||
You have different progress saved locally and on the server for
|
||||
this chapter.
|
||||
@ -258,10 +276,17 @@ const Chapter = () => {
|
||||
</span>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="flex flex-row justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (savedPage && savedPage > 1) {
|
||||
setCurrentPage(savedPage);
|
||||
setVisibleCount(savedPage);
|
||||
setIsAutoScrolling(true);
|
||||
initialJumpDone.current = false;
|
||||
saveLocalProgress(savedPage, true);
|
||||
}
|
||||
setHasInitialized(true);
|
||||
setShowConflictDialog(false);
|
||||
}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user