128 lines
3.2 KiB
TypeScript
128 lines
3.2 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
useGetProgress1,
|
|
useUpdateProgress,
|
|
} from "@/api/generated/reading-progress/reading-progress";
|
|
|
|
interface ReadingTrackerData {
|
|
chapterPage: { [chapterId: number]: number };
|
|
updatedAt?: { [chapterId: number]: string };
|
|
}
|
|
|
|
export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
|
|
const [currentPage, setCurrentPage] = useState<number | null>(() => {
|
|
const jsonString = localStorage.getItem("readingTrackerData");
|
|
if (jsonString) {
|
|
try {
|
|
const data: ReadingTrackerData = JSON.parse(jsonString);
|
|
return data.chapterPage[chapterId] ?? null;
|
|
} catch (e) {
|
|
console.error("Failed to parse local progress", e);
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const lastSyncedPage = useRef<number | null>(null);
|
|
const syncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
const { data: progressData, isLoading: isLoadingProgress } = useGetProgress1(
|
|
mangaId,
|
|
chapterId,
|
|
{ query: { retry: false } },
|
|
);
|
|
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] ?? null;
|
|
setCurrentPage(localPage);
|
|
lastSyncedPage.current = localPage;
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
} else {
|
|
setCurrentPage(null);
|
|
lastSyncedPage.current = null;
|
|
}
|
|
}, [chapterId]);
|
|
|
|
const saveLocalProgress = useCallback(
|
|
(page: number, forceSync = false) => {
|
|
setCurrentPage(page);
|
|
const jsonString = localStorage.getItem("readingTrackerData");
|
|
let data: ReadingTrackerData = { chapterPage: {}, updatedAt: {} };
|
|
if (jsonString) {
|
|
try {
|
|
data = JSON.parse(jsonString);
|
|
} catch (e) {
|
|
// fallback to empty
|
|
}
|
|
}
|
|
data.chapterPage[chapterId] = page;
|
|
data.updatedAt = data.updatedAt || {};
|
|
data.updatedAt[chapterId] = new Date().toISOString();
|
|
localStorage.setItem("readingTrackerData", JSON.stringify(data));
|
|
|
|
// Debounced backend sync
|
|
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
|
|
|
|
const performSync = () => {
|
|
if (forceSync || page !== lastSyncedPage.current) {
|
|
updateProgress({
|
|
data: {
|
|
mangaId,
|
|
chapterId,
|
|
pageNumber: page,
|
|
},
|
|
});
|
|
lastSyncedPage.current = page;
|
|
}
|
|
};
|
|
|
|
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);
|
|
return serverProgress.pageNumber;
|
|
}
|
|
return null;
|
|
}, [serverProgress, saveLocalProgress]);
|
|
|
|
return {
|
|
currentPage,
|
|
serverProgress,
|
|
isLoadingProgress,
|
|
saveLocalProgress,
|
|
applyServerProgress,
|
|
};
|
|
};
|