Update database schema and synchronization logic
- Add `useEffect` in `ScribeLeftBar` for handling book state changes. - Extend `BooksSyncContext` with new properties and stricter typings. - Refine `Repositories` to include `lastUpdate` handling for synchronization processes. - Add comprehensive `fetchComplete*` repository methods for retrieving entity-specific sync data. - Enhance offline logic for chapters, characters, locations, and world synchronization. - Improve error handling across IPC handlers and repositories.
This commit is contained in:
@@ -18,6 +18,7 @@ import {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import CreditCounter from "@/components/CreditMeters";
|
||||
import QuillSense from "@/lib/models/QuillSense";
|
||||
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
||||
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
|
||||
|
||||
export default function ScribeControllerBar() {
|
||||
const {chapter, setChapter} = useContext(ChapterContext);
|
||||
@@ -27,6 +28,7 @@ export default function ScribeControllerBar() {
|
||||
const t = useTranslations();
|
||||
const {lang, setLang} = useContext<LangContextProps>(LangContext);
|
||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext)
|
||||
const {serverOnlyBooks,localOnlyBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
|
||||
const isGPTEnabled: boolean = !isCurrentlyOffline() && QuillSense.isOpenAIEnabled(session);
|
||||
const isGemini: boolean = !isCurrentlyOffline() && QuillSense.isOpenAIEnabled(session);
|
||||
@@ -120,7 +122,7 @@ export default function ScribeControllerBar() {
|
||||
</div>
|
||||
<div className="min-w-[200px]">
|
||||
<SelectBox onChangeCallBack={(e) => getBook(e.target.value)}
|
||||
data={Book.booksToSelectBox(session.user?.books ?? [])} defaultValue={book?.bookId}
|
||||
data={Book.booksToSelectBox([...serverOnlyBooks, ...localOnlyBooks])} defaultValue={book?.bookId}
|
||||
placeholder={t("controllerBar.selectBook")}/>
|
||||
</div>
|
||||
{chapter && (
|
||||
|
||||
@@ -8,14 +8,15 @@ import {SessionContext} from "@/context/SessionContext";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import {BookContext} from "@/context/BookContext";
|
||||
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
|
||||
|
||||
export default function ScribeFooterBar() {
|
||||
const t = useTranslations();
|
||||
const {chapter} = useContext(ChapterContext);
|
||||
const {book} = useContext(BookContext);
|
||||
const editor: Editor | null = useContext(EditorContext).editor;
|
||||
const {session} = useContext(SessionContext);
|
||||
const {errorMessage} = useContext(AlertContext)
|
||||
const {serverOnlyBooks,localOnlyBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
|
||||
const [wordsCount, setWordsCount] = useState<number>(0);
|
||||
|
||||
@@ -91,7 +92,7 @@ export default function ScribeFooterBar() {
|
||||
className="flex items-center gap-2 bg-secondary/50 px-4 py-2 rounded-xl border border-secondary shadow-sm">
|
||||
<FontAwesomeIcon icon={faBook} className={'text-primary w-4 h-4'}/>
|
||||
<span className="text-muted text-sm font-medium mr-1">{t('scribeFooterBar.books')}:</span>
|
||||
<span className="text-text-primary font-bold">{session.user?.books?.length}</span>
|
||||
<span className="text-text-primary font-bold">{(serverOnlyBooks.length+localOnlyBooks.length)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -7,8 +7,9 @@ import System from "@/lib/models/System";
|
||||
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
|
||||
import {LangContext} from "@/context/LangContext";
|
||||
import {CompleteBook} from "@/lib/models/Book";
|
||||
import {SyncType} from "@/context/BooksSyncContext";
|
||||
import {BooksSyncContext, BooksSyncContextProps, SyncType} from "@/context/BooksSyncContext";
|
||||
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
|
||||
import {BookSyncCompare} from "@/lib/models/SyncedBook";
|
||||
|
||||
interface SyncBookProps {
|
||||
bookId: string;
|
||||
@@ -23,6 +24,7 @@ export default function SyncBook({bookId, status}: SyncBookProps) {
|
||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [currentStatus, setCurrentStatus] = useState<SyncType>(status);
|
||||
const {booksToSyncToServer, booksToSyncFromServer} = useContext<BooksSyncContextProps>(BooksSyncContext)
|
||||
|
||||
const isOffline: boolean = isCurrentlyOffline();
|
||||
|
||||
@@ -53,11 +55,67 @@ export default function SyncBook({bookId, status}: SyncBookProps) {
|
||||
}
|
||||
|
||||
async function syncFromServer(): Promise<void> {
|
||||
// TODO: Implement sync from server (server has newer version)
|
||||
if (isOffline) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const bookToFetch:BookSyncCompare|undefined = booksToSyncFromServer.find((book:BookSyncCompare):boolean => book.id === bookId);
|
||||
if (!bookToFetch) {
|
||||
errorMessage(t("bookCard.syncFromServerError"));
|
||||
return;
|
||||
}
|
||||
const response: CompleteBook = await System.authPostToServer('book/sync/server-to-client', {
|
||||
bookToSync: bookToFetch
|
||||
}, session.accessToken, lang);
|
||||
if (!response) {
|
||||
errorMessage(t("bookCard.syncFromServerError"));
|
||||
return;
|
||||
}
|
||||
const syncStatus:boolean = await window.electron.invoke<boolean>('db:book:sync:toClient', response);
|
||||
if (!syncStatus) {
|
||||
errorMessage(t("bookCard.syncFromServerError"));
|
||||
return;
|
||||
}
|
||||
setCurrentStatus('synced');
|
||||
} catch (e:unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t("bookCard.syncFromServerError"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function syncToServer(): Promise<void> {
|
||||
// TODO: Implement sync to server (local has newer version)
|
||||
if (isOffline) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const bookToFetch:BookSyncCompare|undefined = booksToSyncToServer.find((book:BookSyncCompare):boolean => book.id === bookId);
|
||||
if (!bookToFetch) {
|
||||
errorMessage(t("bookCard.syncToServerError"));
|
||||
return;
|
||||
}
|
||||
const bookToSync: CompleteBook = await window.electron.invoke<CompleteBook>('db:book:sync:toServer', bookToFetch);
|
||||
if (!bookToSync) {
|
||||
errorMessage(t("bookCard.syncToServerError"));
|
||||
return;
|
||||
}
|
||||
const response: boolean = await System.authPutToServer('book/sync/client-to-server', {
|
||||
book: bookToSync
|
||||
}, session.accessToken, lang);
|
||||
if (!response) {
|
||||
errorMessage(t("bookCard.syncToServerError"));
|
||||
return;
|
||||
}
|
||||
setCurrentStatus('synced');
|
||||
} catch (e:unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t("bookCard.syncToServerError"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -15,7 +15,7 @@ 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";
|
||||
import {BookSyncCompare, SyncedBook} from "@/lib/models/SyncedBook";
|
||||
|
||||
export default function BookList() {
|
||||
const {session, setSession} = useContext(SessionContext);
|
||||
@@ -190,10 +190,10 @@ export default function BookList() {
|
||||
if (localOnlyBooks.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
return 'local-only';
|
||||
}
|
||||
if (booksToSyncFromServer.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
if (booksToSyncFromServer.find((book: BookSyncCompare):boolean => book.id === bookId)) {
|
||||
return 'to-sync-from-server';
|
||||
}
|
||||
if (booksToSyncToServer.find((book: SyncedBook):boolean => book.id === bookId)) {
|
||||
if (booksToSyncToServer.find((book: BookSyncCompare):boolean => book.id === bookId)) {
|
||||
return 'to-sync-to-server';
|
||||
}
|
||||
return 'synced';
|
||||
|
||||
@@ -36,6 +36,7 @@ export default function ScribeChapterComponent() {
|
||||
const scrollContainerRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
if (book)
|
||||
getChapterList().then();
|
||||
}, [book]);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faBookMedical, faBookOpen, faFeather} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useContext, useState} from "react";
|
||||
import {useContext, useEffect, useState} from "react";
|
||||
import {BookContext} from "@/context/BookContext";
|
||||
import ScribeChapterComponent from "@/components/leftbar/ScribeChapterComponent";
|
||||
import PanelHeader from "@/components/PanelHeader";
|
||||
@@ -75,6 +75,14 @@ export default function ScribeLeftBar() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(():void => {
|
||||
if (!book){
|
||||
setCurrentPanel(undefined);
|
||||
setPanelHidden(false);
|
||||
return;
|
||||
}
|
||||
}, [book]);
|
||||
|
||||
return (
|
||||
<div id="left-panel-container" data-guide={"left-panel-container"} className="flex transition-all duration-300">
|
||||
<div className="bg-tertiary border-r border-secondary/50 p-3 flex flex-col space-y-3 shadow-xl">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faFeather, faGlobe, faInfoCircle, faMapMarkerAlt, faUsers} from "@fortawesome/free-solid-svg-icons";
|
||||
import {RefObject, useContext, useRef, useState} from "react";
|
||||
import {RefObject, useContext, useEffect, useRef, useState} from "react";
|
||||
import {BookContext} from "@/context/BookContext";
|
||||
import {ChapterContext} from "@/context/ChapterContext";
|
||||
import {PanelComponent} from "@/lib/models/Editor";
|
||||
import PanelHeader from "@/components/PanelHeader";
|
||||
import AboutEditors from "@/components/rightbar/AboutERitors";
|
||||
@@ -57,6 +56,14 @@ export default function ComposerRightBar() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(():void => {
|
||||
if (!book){
|
||||
setCurrentPanel(undefined);
|
||||
setPanelHidden(false);
|
||||
return;
|
||||
}
|
||||
}, [book]);
|
||||
|
||||
const editorComponents: PanelComponent[] = [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
Reference in New Issue
Block a user