Refactor and extend offline synchronization logic across components and services

- Integrated sync queue mechanisms with `LocalSyncQueueContext` for offline data handling.
- Updated key sync-related services (e.g., book, chapter, series) to support offline-first functionality.
- Removed redundant database fetch methods to optimize repository logic and improve maintainability.
- Enhanced Tauri IPC usage for sync operations and removed legacy methods in Rust services.
This commit is contained in:
natreex
2026-03-30 21:06:58 -04:00
parent b9606e899a
commit dbbe33b19b
22 changed files with 295 additions and 293 deletions

View File

@@ -20,6 +20,9 @@ import {LangContext, LangContextProps} from "@/context/LangContext";
import {BookProps} from "@/lib/types/book";
import {SettingRef} from "@/lib/types/settings";
import ImageDropZone from "@/components/form/ImageDropZone";
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
import {SyncedBook} from "@/lib/types/synced-book";
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from "@/context/SyncQueueContext";
function BasicInformationSetting(_props: object, ref: React.ForwardedRef<SettingRef>): React.JSX.Element {
const t = useTranslations();
@@ -30,6 +33,8 @@ function BasicInformationSetting(_props: object, ref: React.ForwardedRef<Setting
const userToken: string = session?.accessToken ? session?.accessToken : '';
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const bookId: string = book?.bookId ? book?.bookId.toString() : '';
const [currentImage, setCurrentImage] = useState<string>(book?.coverImage ?? '');
@@ -120,14 +125,14 @@ function BasicInformationSetting(_props: object, ref: React.ForwardedRef<Setting
wordCount: wordCount,
});
} else {
response = await apiPost<boolean>('book/basic-information', {
title: title,
subTitle: subTitle,
summary: summary,
publicationDate: publicationDate,
wordCount: wordCount,
bookId: bookId
}, userToken, lang);
const basicInfoData = {
title, subTitle, summary, publicationDate, wordCount, bookId
};
response = await apiPost<boolean>('book/basic-information', basicInfoData, userToken, lang);
if (isDesktop && localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) {
addToQueue('update_book_basic_info', basicInfoData);
}
}
if (!response) {
errorMessage(t('basicInformationSetting.error.update'));

View File

@@ -22,31 +22,44 @@ export default function DeleteBook({bookId}: DeleteBookProps) {
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext)
const [showConfirmBox, setShowConfirmBox] = useState<boolean>(false);
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext)
const {setServerSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext)
const {serverOnlyBooks, setServerOnlyBooks, localOnlyBooks, setLocalOnlyBooks, localSyncedBooks, setLocalSyncedBooks, setServerSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext)
const [deleteLocalToo, setDeleteLocalToo] = useState<boolean>(false);
const ifLocalOnlyBook: SyncedBook | undefined = localOnlyBooks.find((b: SyncedBook): boolean => b.id === bookId);
const ifSyncedBook: SyncedBook | undefined = localSyncedBooks.find((b: SyncedBook): boolean => b.id === bookId);
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
function handleConfirmation(): void {
setDeleteLocalToo(false);
setShowConfirmBox(true);
}
async function handleDeleteBook(): Promise<void> {
try {
let response: boolean;
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.deleteBook(bookId, Date.now());
const deletedAt: number = Math.floor(Date.now() / 1000);
if (isDesktop && (isCurrentlyOffline() || ifLocalOnlyBook)) {
response = await tauri.deleteBook(bookId, deletedAt);
} else {
response = await apiDelete<boolean>('book/delete', {
id: bookId,
}, session.accessToken, lang);
response = await apiDelete<boolean>('book/delete', {id: bookId, deletedAt}, session.accessToken, lang);
if (response && isDesktop && ifSyncedBook && deleteLocalToo) {
await tauri.deleteBook(bookId, deletedAt);
}
}
if (response) {
setShowConfirmBox(false);
if (!response) {
errorMessage("Une erreur est survenue lors de la suppression du livre.");
return;
if (ifLocalOnlyBook) {
setLocalOnlyBooks(localOnlyBooks.filter((b: SyncedBook): boolean => b.id !== bookId));
} else if (ifSyncedBook) {
setLocalSyncedBooks(localSyncedBooks.filter((b: SyncedBook): boolean => b.id !== bookId));
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => prev.filter((b: SyncedBook): boolean => b.id !== bookId));
if (!deleteLocalToo) {
setLocalOnlyBooks([...localOnlyBooks, ifSyncedBook]);
}
} else {
setServerOnlyBooks(serverOnlyBooks.filter((b: SyncedBook): boolean => b.id !== bookId));
}
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => prev.filter((book: SyncedBook): boolean => book.id !== bookId))
}
} catch (e: unknown) {
if (e instanceof Error) {

View File

@@ -5,6 +5,9 @@ import {SessionContext, SessionContextProps} from "@/context/SessionContext";
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import {BookContext, BookContextProps} from "@/context/BookContext";
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {isDesktop} from '@/lib/configs';
import {apiDelete, apiGet, apiPatch, apiPost} from '@/lib/api/client';
import * as tauri from '@/lib/tauri';
@@ -57,6 +60,8 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
const {successMessage, errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {book, setBook}: BookContextProps = useContext<BookContextProps>(BookContext);
const {isCurrentlyOffline} = useContext(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const currentEntityId: string = entityId || book?.bookId || '';
const useLocal: boolean = isDesktop && (isCurrentlyOffline() || !!book?.localBook);
@@ -120,6 +125,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
toolName: 'locations',
enabled: enabled
}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('update_book_tool_setting', {bookId: currentEntityId, toolName: 'locations', enabled});
}
if (response && setBook && book) {
setToolEnabled(enabled);
setBook({
@@ -218,6 +226,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
sectionId = useLocal
? await tauri.addLocationSection(newSectionName, currentEntityId)
: await apiPost<string>('location/section/add', {bookId: currentEntityId, locationName: newSectionName}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('add_location_section', {bookId: currentEntityId, sectionId, locationName: newSectionName});
}
if (!sectionId) {
errorMessage(t('locationComponent.errorUnknownAddSection'));
return;
@@ -258,6 +269,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
elementId = useLocal
? await tauri.addLocationElement(sectionId, newElementNames[sectionId])
: await apiPost<string>('location/element/add', {bookId: currentEntityId, locationId: sectionId, elementName: newElementNames[sectionId]}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('add_location_element', {bookId: currentEntityId, locationId: sectionId, elementId, elementName: newElementNames[sectionId]});
}
if (!elementId) {
errorMessage(t('locationComponent.errorUnknownAddElement'));
return;
@@ -325,6 +339,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
subElementId = useLocal
? await tauri.addLocationSubElement(parentElementId, newSubElementNames[elementIndex])
: await apiPost<string>('location/sub-element/add', {elementId: parentElementId, subElementName: newSubElementNames[elementIndex]}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('add_location_subelement', {elementId: parentElementId, subElementId, subElementName: newSubElementNames[elementIndex]});
}
if (!subElementId) {
errorMessage(t('locationComponent.errorUnknownAddSubElement'));
return;
@@ -381,6 +398,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
success = useLocal
? await tauri.deleteLocationElement(elementId!, currentEntityId, deletedAt)
: await apiDelete<boolean>('location/element/delete', {elementId: elementId}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('delete_location_element', {elementId, bookId: currentEntityId, deletedAt});
}
}
if (!success) {
errorMessage(t('locationComponent.errorUnknownDeleteElement'));
@@ -417,6 +437,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
success = useLocal
? await tauri.deleteLocationSubElement(subElementId!, currentEntityId, deletedAt)
: await apiDelete<boolean>('location/sub-element/delete', {subElementId: subElementId}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('delete_location_subelement', {subElementId, bookId: currentEntityId, deletedAt});
}
}
if (!success) {
errorMessage(t('locationComponent.errorUnknownDeleteSubElement'));
@@ -447,6 +470,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
success = useLocal
? await tauri.deleteLocationSection(sectionId, currentEntityId, deletedAt)
: await apiDelete<boolean>('location/delete', {locationId: sectionId}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('delete_location', {locationId: sectionId, bookId: currentEntityId, deletedAt});
}
}
if (!success) {
errorMessage(t('locationComponent.errorUnknownDeleteSection'));
@@ -468,6 +494,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
const response: boolean = useLocal
? await tauri.updateLocations(sections) as boolean
: await apiPost<boolean>(`location/update`, {locations: sections}, token, lang);
if (!useLocal && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('update_locations', {locations: sections});
}
if (!response) {
errorMessage(t('locationComponent.errorUnknownSave'));
return;

View File

@@ -6,6 +6,9 @@ import {apiDelete, apiPost} from '@/lib/api/client';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
@@ -30,6 +33,8 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const bookId: string | undefined = book?.bookId;
const token: string = session.accessToken;
@@ -72,10 +77,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
incidentId = await tauri.addIncident(bookId ?? '', newIncidentTitle);
} else {
incidentId = await apiPost<string>('book/incident/new', {
bookId,
name: newIncidentTitle,
}, token, lang);
const addData = {bookId, name: newIncidentTitle};
incidentId = await apiPost<string>('book/incident/new', addData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('add_incident', addData);
}
}
if (!incidentId) {
errorMessage(t('errorAddIncident'));
@@ -114,10 +121,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removeIncident(bookId ?? '', incidentId, Date.now());
} else {
response = await apiDelete<boolean>('book/incident/remove', {
bookId,
incidentId,
}, token, lang);
const deleteData = {bookId, incidentId};
response = await apiDelete<boolean>('book/incident/remove', deleteData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('remove_incident', {...deleteData, deletedAt: Date.now()});
}
}
if (!response) {
errorMessage(t('errorDeleteIncident'));
@@ -151,11 +160,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
plotId = await tauri.addPlotPoint(bookId ?? '', newPlotPointTitle, selectedIncidentId);
} else {
plotId = await apiPost<string>('book/plot/new', {
bookId,
name: newPlotPointTitle,
incidentId: selectedIncidentId,
}, token, lang);
const plotData = {bookId, name: newPlotPointTitle, incidentId: selectedIncidentId};
plotId = await apiPost<string>('book/plot/new', plotData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('add_plot_point', plotData);
}
}
if (!plotId) {
errorMessage(t('errorAddPlotPoint'));
@@ -195,9 +205,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removePlotPoint(plotPointId, bookId ?? '', Date.now());
} else {
response = await apiDelete<boolean>('book/plot/remove', {
plotId: plotPointId,
}, token, lang);
const deleteData = {plotId: plotPointId};
response = await apiDelete<boolean>('book/plot/remove', deleteData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('remove_plot_point', {...deleteData, bookId, deletedAt: Date.now()});
}
}
if (!response) {
errorMessage(t('errorDeletePlotPoint'));
@@ -246,13 +259,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
incidentId: destination === 'incident' ? itemId : undefined,
});
} else {
linkId = await apiPost<string>('chapter/resume/add', {
bookId,
chapterId: chapterId,
actId: actId,
plotId: destination === 'plotPoint' ? itemId : null,
incidentId: destination === 'incident' ? itemId : null,
}, token, lang);
const linkData = {bookId, chapterId, actId, plotId: destination === 'plotPoint' ? itemId : null, incidentId: destination === 'incident' ? itemId : null};
linkId = await apiPost<string>('chapter/resume/add', linkData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('add_chapter_information', linkData);
}
}
if (!linkId) {
errorMessage(t('errorLinkChapter'));
@@ -332,9 +344,12 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removeChapterInformation(chapterInfoId, bookId ?? '', Date.now());
} else {
response = await apiDelete<boolean>('chapter/resume/remove', {
chapterInfoId,
}, token, lang);
const removeData = {chapterInfoId};
response = await apiDelete<boolean>('chapter/resume/remove', removeData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('remove_chapter_information', {...removeData, bookId, deletedAt: Date.now()});
}
}
if (!response) {
errorMessage(t('errorUnlinkChapter'));

View File

@@ -6,6 +6,9 @@ import {apiDelete, apiPost} from '@/lib/api/client';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
@@ -27,6 +30,8 @@ export default function Issues({issues, setIssues}: IssuesProps) {
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const bookId: string | undefined = book?.bookId;
const token: string = session.accessToken;
@@ -43,10 +48,12 @@ export default function Issues({issues, setIssues}: IssuesProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
issueId = await tauri.addIssue(bookId ?? '', newIssueName);
} else {
issueId = await apiPost<string>('book/issue/add', {
bookId,
name: newIssueName,
}, token, lang);
const addData = {bookId, name: newIssueName};
issueId = await apiPost<string>('book/issue/add', addData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('add_issue', addData);
}
}
if (!issueId) {
errorMessage(t("issues.errorAdd"));
@@ -79,15 +86,12 @@ export default function Issues({issues, setIssues}: IssuesProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removeIssue(bookId ?? '', issueId, Date.now());
} else {
response = await apiDelete<boolean>(
'book/issue/remove',
{
bookId,
issueId,
},
token,
lang
);
const deleteData = {bookId, issueId};
response = await apiDelete<boolean>('book/issue/remove', deleteData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('remove_issue', {...deleteData, deletedAt: Date.now()});
}
}
if (response) {
const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,);

View File

@@ -7,6 +7,9 @@ import {apiDelete, apiPost} from '@/lib/api/client';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
@@ -30,6 +33,8 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const bookId: string | undefined = book?.bookId;
const token: string = session.accessToken;
@@ -88,15 +93,12 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removeChapter(chapterIdToRemove, bookId ?? '', Date.now());
} else {
response = await apiDelete<boolean>(
'chapter/remove',
{
bookId,
chapterId: chapterIdToRemove,
},
token,
lang,
);
const deleteData = {bookId, chapterId: chapterIdToRemove};
response = await apiDelete<boolean>('chapter/remove', deleteData, token, lang);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('remove_chapter', {...deleteData, deletedAt: Date.now()});
}
}
if (!response) {
errorMessage(t("mainChapter.errorDelete"));
@@ -126,16 +128,12 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
chapterOrder: newChapterOrder ? newChapterOrder : 0,
});
} else {
responseId = await apiPost<string>(
'chapter/add',
{
bookId: bookId,
wordsCount: 0,
chapterOrder: newChapterOrder ? newChapterOrder : 0,
title: newChapterTitle,
},
token,
);
const addData = {bookId, wordsCount: 0, chapterOrder: newChapterOrder ? newChapterOrder : 0, title: newChapterTitle};
responseId = await apiPost<string>('chapter/add', addData, token);
if (isDesktop && bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('add_chapter', addData);
}
}
if (!responseId) {
errorMessage(t("mainChapter.errorAdd"));

View File

@@ -8,6 +8,9 @@ import {apiGet, apiPost} from '@/lib/api/client';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {Act as ActType, Incident, Issue, PlotPoint} from '@/lib/types/book';
import {ActChapter, ChapterListProps} from '@/lib/types/chapter';
import MainChapter from "@/components/book/settings/story/MainChapter";
@@ -51,6 +54,8 @@ export function Story(_props: object, ref: React.ForwardedRef<SettingRef>): Reac
const userToken: string = session.accessToken;
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const [acts, setActs] = useState<ActType[]>([]);
const [issues, setIssues] = useState<Issue[]>([]);
@@ -138,12 +143,12 @@ export function Story(_props: object, ref: React.ForwardedRef<SettingRef>): Reac
issues,
});
} else {
response = await apiPost<boolean>('book/story', {
bookId,
acts,
mainChapters,
issues,
}, userToken, lang);
const storyData = {bookId, acts, mainChapters, issues};
response = await apiPost<boolean>('book/story', storyData, userToken, lang);
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === bookId)) {
addToQueue('update_book_story', storyData);
}
}
if (!response) {
errorMessage(t("story.errorSave"))

View File

@@ -11,6 +11,9 @@ import {apiDelete, apiPost} from "@/lib/api/client";
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import InputField from "@/components/form/InputField";
import {useTranslations} from '@/lib/i18n';
@@ -52,6 +55,8 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const [newElementName, setNewElementName] = useState<string>('');
@@ -69,6 +74,9 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
response = await apiDelete<boolean>(endpoint, {
elementId: elements[index].id,
}, session.accessToken, lang);
if (!isSeriesMode && isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
addToQueue('remove_world_element', {elementId: elements[index].id, bookId: book?.bookId, deletedAt: Date.now()});
}
}
if (!response) {
errorMessage(t("worldSetting.unknownError"))
@@ -119,6 +127,9 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
worldId: worlds[selectedWorldIndex].id,
elementName: newElementName,
}, session.accessToken, lang);
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
addToQueue('add_world_element', {worldId: worlds[selectedWorldIndex].id, elementId, elementType: section, elementName: newElementName});
}
if (!elementId) {
errorMessage(t("worldSetting.unknownError"))
return;

View File

@@ -9,6 +9,9 @@ import {apiGet, apiPatch, apiPost} from "@/lib/api/client";
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/types/synced-book';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {ElementSection, WorldListResponse, WorldProps, WorldTextField} from "@/lib/types/world";
import {SettingRef} from "@/lib/types/settings";
import {elementSections} from "@/lib/constants/world";
@@ -39,6 +42,8 @@ export function WorldSetting(props: WorldSettingProps, ref: React.ForwardedRef<S
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {book, setBook}: BookContextProps = useContext<BookContextProps>(BookContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const currentEntityId: string = entityId || book?.bookId || '';
const isSeriesMode: boolean = entityType === 'series';
@@ -96,6 +101,9 @@ export function WorldSetting(props: WorldSettingProps, ref: React.ForwardedRef<S
toolName: 'worlds',
enabled: enabled
}, session.accessToken, lang);
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('update_book_tool_setting', {bookId: currentEntityId, toolName: 'worlds', enabled});
}
}
if (response && setBook && book) {
setToolEnabled(enabled);
@@ -238,6 +246,9 @@ export function WorldSetting(props: WorldSettingProps, ref: React.ForwardedRef<S
worldName: newWorldName,
bookId: currentEntityId,
}, session.accessToken, lang);
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('add_world', {worldName: newWorldName, bookId: currentEntityId, worldId});
}
if (!worldId) {
errorMessage(t("worldSetting.addWorldError"));
return;
@@ -310,6 +321,9 @@ export function WorldSetting(props: WorldSettingProps, ref: React.ForwardedRef<S
world: currentWorld,
bookId: currentEntityId,
}, session.accessToken, lang);
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('update_world', {world: currentWorld, bookId: currentEntityId});
}
if (!response) {
errorMessage(t("worldSetting.updateWorldError"));
return;
@@ -393,7 +407,10 @@ export function WorldSetting(props: WorldSettingProps, ref: React.ForwardedRef<S
errorMessage(t("worldSetting.importError"));
return;
}
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === currentEntityId)) {
addToQueue('add_world', {worldName: seriesWorld.name, bookId: currentEntityId, worldId, seriesWorldId});
}
const newWorld: WorldProps = {
id: worldId,
name: seriesWorld.name,