Add BooksSyncContext, refine database schema, and enhance synchronization support
- Introduce `BooksSyncContext` for managing book synchronization states (server-only, local-only, to-sync, etc.). - Remove `UserContext` and related dependencies. - Refine localization strings (`en.json`) with sync-related updates (e.g., "toSyncFromServer", "toSyncToServer"). - Extend database schema with additional tables and fields for syncing books, chapters, and related entities. - Add `last_update` fields and update corresponding repository methods to support synchronization logic. - Enhance IPC handlers with stricter typing, data validation, and sync-aware operations.
This commit is contained in:
@@ -14,6 +14,8 @@ import User from "@/lib/models/User";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
||||
import {BooksSyncContext, BooksSyncContextProps, SyncType} from "@/context/BooksSyncContext";
|
||||
import {SyncedBook} from "@/lib/models/SyncedBook";
|
||||
|
||||
export default function BookList() {
|
||||
const {session, setSession} = useContext(SessionContext);
|
||||
@@ -23,6 +25,7 @@ export default function BookList() {
|
||||
const t = useTranslations();
|
||||
const {lang} = useContext<LangContextProps>(LangContext)
|
||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext)
|
||||
const {booksToSyncFromServer, booksToSyncToServer, serverOnlyBooks, localOnlyBooks} = useContext<BooksSyncContextProps>(BooksSyncContext)
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [groupedBooks, setGroupedBooks] = useState<Record<string, BookProps[]>>({});
|
||||
@@ -86,7 +89,7 @@ export default function BookList() {
|
||||
|
||||
useEffect((): void => {
|
||||
getBooks().then()
|
||||
}, [session.user?.books]);
|
||||
}, [booksToSyncFromServer, booksToSyncToServer, serverOnlyBooks, localOnlyBooks]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (accessToken) getBooks().then();
|
||||
@@ -115,11 +118,21 @@ export default function BookList() {
|
||||
async function getBooks(): Promise<void> {
|
||||
setIsLoadingBooks(true);
|
||||
try {
|
||||
let bookResponse: BookListProps[] = [];
|
||||
let bookResponse: (BookListProps & { itIsLocal: boolean })[] = [];
|
||||
if (!isCurrentlyOffline()) {
|
||||
bookResponse = await System.authGetQueryToServer<BookListProps[]>('books', accessToken, lang);
|
||||
const [onlineBooks, localBooks]: [BookListProps[], BookListProps[]] = await Promise.all([
|
||||
System.authGetQueryToServer<BookListProps[]>('books', accessToken, lang),
|
||||
window.electron.invoke<BookListProps[]>('db:book:books')
|
||||
]);
|
||||
const onlineBookIds: Set<string> = new Set(onlineBooks.map((book: BookListProps): string => book.id));
|
||||
const uniqueLocalBooks: BookListProps[] = localBooks.filter((book: BookListProps): boolean => !onlineBookIds.has(book.id));
|
||||
bookResponse = [
|
||||
...onlineBooks.map((book: BookListProps): BookListProps & { itIsLocal: boolean } => ({ ...book, itIsLocal: false })),
|
||||
...uniqueLocalBooks.map((book: BookListProps): BookListProps & { itIsLocal: boolean } => ({ ...book, itIsLocal: true }))
|
||||
];
|
||||
} else {
|
||||
bookResponse = await window.electron.invoke<BookListProps[]>('db:book:books');
|
||||
const localBooks: BookListProps[] = await window.electron.invoke<BookListProps[]>('db:book:books');
|
||||
bookResponse = localBooks.map((book: BookListProps): BookListProps & { itIsLocal: boolean } => ({ ...book, itIsLocal: true }));
|
||||
}
|
||||
if (bookResponse) {
|
||||
const booksByType: Record<string, BookProps[]> = bookResponse.reduce((groups: Record<string, BookProps[]>, book: BookListProps): Record<string, BookProps[]> => {
|
||||
@@ -170,6 +183,22 @@ export default function BookList() {
|
||||
{}
|
||||
);
|
||||
|
||||
function detectBookSyncStatus(bookId: string):SyncType {
|
||||
if (serverOnlyBooks.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
return 'server-only';
|
||||
}
|
||||
if (localOnlyBooks.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
return 'local-only';
|
||||
}
|
||||
if (booksToSyncFromServer.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
return 'to-sync-from-server';
|
||||
}
|
||||
if (booksToSyncToServer.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
return 'to-sync-to-server';
|
||||
}
|
||||
return 'synced';
|
||||
}
|
||||
|
||||
async function getBook(bookId: string): Promise<void> {
|
||||
try {
|
||||
let bookResponse: BookListProps|null = null;
|
||||
@@ -267,8 +296,10 @@ export default function BookList() {
|
||||
{...(idx === 0 && {'data-guide': 'book-card'})}
|
||||
className={`w-full sm:w-1/3 md:w-1/4 lg:w-1/5 xl:w-1/6 p-2 box-border ${User.guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}>
|
||||
<BookCard book={book}
|
||||
syncStatus={detectBookSyncStatus(book.bookId)}
|
||||
onClickCallback={getBook}
|
||||
index={idx}/>
|
||||
index={idx}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user