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:
@@ -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'));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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,);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -26,6 +26,9 @@ import {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 {AlertContext, AlertContextProps} from '@/context/AlertContext';
|
||||
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
|
||||
import DraftCompanion from "@/components/editor/DraftCompanion";
|
||||
@@ -144,7 +147,9 @@ export default function TextEditor() {
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
||||
|
||||
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
||||
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
|
||||
const [mainTimer, setMainTimer] = useState<number>(0);
|
||||
const [showDraftCompanion, setShowDraftCompanion] = useState<boolean>(false);
|
||||
const [showGhostWriter, setShowGhostWriter] = useState<boolean>(false);
|
||||
@@ -291,13 +296,18 @@ export default function TextEditor() {
|
||||
contentId: '',
|
||||
});
|
||||
} else {
|
||||
response = await apiPost<boolean>(`chapter/content`, {
|
||||
const saveData = {
|
||||
chapterId,
|
||||
version,
|
||||
content,
|
||||
totalWordCount: editor.getText().length,
|
||||
currentTime: mainTimer
|
||||
}, session?.accessToken ?? '');
|
||||
};
|
||||
response = await apiPost<boolean>(`chapter/content`, saveData, session?.accessToken ?? '');
|
||||
|
||||
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
|
||||
addToQueue('save_chapter_content', saveData);
|
||||
}
|
||||
}
|
||||
if (!response) {
|
||||
errorMessage(t('editor.error.savedFailed'));
|
||||
@@ -315,7 +325,7 @@ export default function TextEditor() {
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [editor, chapter, mainTimer, session?.accessToken, successMessage, errorMessage]);
|
||||
}, [editor, chapter, mainTimer, session?.accessToken, successMessage, errorMessage, addToQueue, book?.localBook, isCurrentlyOffline]);
|
||||
|
||||
const handleShowDraftCompanion: () => void = useCallback((): void => {
|
||||
setShowDraftCompanion((prev: boolean): boolean => !prev);
|
||||
@@ -454,7 +464,7 @@ export default function TextEditor() {
|
||||
onClick={handleShowUserSettings}
|
||||
tooltip={t("textEditor.preferences")}
|
||||
/>
|
||||
{chapter?.chapterContent.version === 2 && book?.quillsenseEnabled !== false && (
|
||||
{chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && !book?.localBook && book?.quillsenseEnabled !== false && (
|
||||
<IconButton
|
||||
icon={Ghost}
|
||||
variant="ghost"
|
||||
|
||||
@@ -6,6 +6,13 @@ import {SessionContext, SessionContextProps} from '@/context/SessionContext';
|
||||
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
|
||||
import {LangContext, LangContextProps} from '@/context/LangContext';
|
||||
import {apiPost} from '@/lib/api/client';
|
||||
import {isDesktop} from '@/lib/configs';
|
||||
import * as tauri from '@/lib/tauri';
|
||||
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
|
||||
import {BookContext, BookContextProps} from '@/context/BookContext';
|
||||
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
|
||||
import {SyncedBook} from '@/lib/types/synced-book';
|
||||
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
|
||||
import IconButton from '@/components/ui/IconButton';
|
||||
|
||||
export type SyncElementType = 'character' | 'world' | 'location' | 'spell';
|
||||
@@ -42,7 +49,11 @@ export default function SyncFieldWrapper({
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
|
||||
|
||||
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
||||
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
||||
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
|
||||
|
||||
const [isUploading, setIsUploading] = useState<boolean>(false);
|
||||
|
||||
const isLinkedToSeries: boolean = !!seriesElementId;
|
||||
@@ -53,17 +64,17 @@ export default function SyncFieldWrapper({
|
||||
|
||||
setIsUploading(true);
|
||||
try {
|
||||
const response: SeriesSyncUploadResponse = await apiPost<SeriesSyncUploadResponse>(
|
||||
'series/propagate',
|
||||
{
|
||||
type: elementType,
|
||||
bookElementId: bookElementId,
|
||||
field: field,
|
||||
value: currentValue
|
||||
},
|
||||
session.accessToken,
|
||||
lang
|
||||
);
|
||||
const requestData = {type: elementType, bookElementId, field, value: currentValue};
|
||||
let response: SeriesSyncUploadResponse;
|
||||
|
||||
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
|
||||
response = await tauri.invoke<SeriesSyncUploadResponse>('series_sync_upload', {data: requestData});
|
||||
} else {
|
||||
response = await apiPost<SeriesSyncUploadResponse>('series/propagate', requestData, session.accessToken, lang);
|
||||
if (isDesktop && book?.bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book.bookId)) {
|
||||
addToQueue('series_sync_upload', requestData);
|
||||
}
|
||||
}
|
||||
if (response.success) {
|
||||
successMessage(t('syncField.uploadSuccess', {count: response.updatedCount}));
|
||||
if (onSyncComplete) {
|
||||
|
||||
@@ -4,6 +4,9 @@ import {apiDelete, 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 {BookContext, BookContextProps} from "@/context/BookContext";
|
||||
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
|
||||
import {ChapterContext, ChapterContextProps} from "@/context/ChapterContext";
|
||||
@@ -26,6 +29,8 @@ export default function ScribeChapterComponent() {
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const userToken: string = session?.accessToken ? session?.accessToken : '';
|
||||
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
||||
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
||||
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const router = useRouter();
|
||||
|
||||
const [chapters, setChapters] = useState<ChapterListProps[]>([])
|
||||
@@ -107,11 +112,12 @@ export default function ScribeChapterComponent() {
|
||||
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
|
||||
response = await tauri.updateChapter(chapterId, title, chapterOrder);
|
||||
} else {
|
||||
response = await apiPost<boolean>('chapter/update', {
|
||||
chapterId: chapterId,
|
||||
chapterOrder: chapterOrder,
|
||||
title: title,
|
||||
}, userToken, lang);
|
||||
const updateData = {chapterId, chapterOrder, title};
|
||||
response = await apiPost<boolean>('chapter/update', updateData, userToken, lang);
|
||||
|
||||
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
|
||||
addToQueue('update_chapter', updateData);
|
||||
}
|
||||
}
|
||||
if (!response) {
|
||||
errorMessage(t("scribeChapterComponent.errorChapterUpdate"));
|
||||
@@ -148,9 +154,12 @@ export default function ScribeChapterComponent() {
|
||||
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
|
||||
response = await tauri.removeChapter(removeChapterId, book?.bookId ?? '', Date.now());
|
||||
} else {
|
||||
response = await apiDelete<boolean>('chapter/remove', {
|
||||
chapterId: removeChapterId,
|
||||
}, userToken, lang);
|
||||
const deleteData = {bookId: book?.bookId, chapterId: removeChapterId};
|
||||
response = await apiDelete<boolean>('chapter/remove', deleteData, userToken, lang);
|
||||
|
||||
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
|
||||
addToQueue('remove_chapter', {...deleteData, deletedAt: Date.now()});
|
||||
}
|
||||
}
|
||||
if (!response) {
|
||||
errorMessage(t("scribeChapterComponent.errorChapterDelete"));
|
||||
@@ -184,11 +193,12 @@ export default function ScribeChapterComponent() {
|
||||
chapterOrder: chapterOrder,
|
||||
});
|
||||
} else {
|
||||
chapterId = await apiPost<string>('chapter/add', {
|
||||
bookId: book?.bookId,
|
||||
chapterOrder: chapterOrder,
|
||||
title: chapterTitle
|
||||
}, userToken, lang);
|
||||
const addData = {bookId: book?.bookId, chapterOrder, title: chapterTitle};
|
||||
chapterId = await apiPost<string>('chapter/add', addData, userToken, lang);
|
||||
|
||||
if (isDesktop && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
|
||||
addToQueue('add_chapter', addData);
|
||||
}
|
||||
}
|
||||
if (!chapterId) {
|
||||
errorMessage(t("scribeChapterComponent.errorChapterSubmit", {chapterName: newChapterName}));
|
||||
|
||||
@@ -30,8 +30,10 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
const {isCurrentlyOffline} = useContext(OfflineContext);
|
||||
const {
|
||||
serverSyncedBooks,
|
||||
setServerSyncedBooks
|
||||
setServerSyncedBooks,
|
||||
localSyncedBooks
|
||||
}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const useLocal: boolean = isDesktop && isCurrentlyOffline();
|
||||
const [name, setName] = useState<string>('');
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [selectedBookIds, setSelectedBookIds] = useState<string[]>([]);
|
||||
@@ -83,6 +85,8 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
: book
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev]);
|
||||
}
|
||||
|
||||
if (onSeriesCreated) {
|
||||
@@ -150,7 +154,7 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
)}
|
||||
</div>
|
||||
|
||||
{serverSyncedBooks.length === 0 ? (
|
||||
{(useLocal ? localSyncedBooks : serverSyncedBooks).length === 0 ? (
|
||||
<div className="text-center py-6 text-muted">
|
||||
<Book className="w-8 h-8 mb-2 opacity-50" strokeWidth={1.75}/>
|
||||
<p>{t("addNewSeriesForm.noBooks")}</p>
|
||||
@@ -158,7 +162,7 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
) : (
|
||||
<div
|
||||
className="max-h-48 overflow-y-auto rounded-xl border border-secondary bg-tertiary">
|
||||
{serverSyncedBooks.map((book: SyncedBook) => {
|
||||
{(useLocal ? localSyncedBooks : serverSyncedBooks).map((book: SyncedBook) => {
|
||||
const isSelected: boolean = selectedBookIds.includes(book.id);
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -10,6 +10,10 @@ import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
|
||||
import {isDesktop} from '@/lib/configs';
|
||||
import {apiDelete} from '@/lib/api/client';
|
||||
import {deleteSeries} from '@/lib/tauri';
|
||||
import {SeriesContext, SeriesContextProps} from '@/context/SeriesContext';
|
||||
import {SeriesSyncContext, SeriesSyncContextProps} from '@/context/SeriesSyncContext';
|
||||
import {SyncedSeries} from '@/lib/types/synced-series';
|
||||
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
|
||||
|
||||
interface SeriesSettingOption {
|
||||
id: string;
|
||||
@@ -36,6 +40,8 @@ export default function SeriesSettingSidebar(
|
||||
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {isCurrentlyOffline} = useContext(OfflineContext);
|
||||
const {localSyncedSeries}: SeriesSyncContextProps = useContext<SeriesSyncContextProps>(SeriesSyncContext);
|
||||
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
||||
const userToken: string = session?.accessToken ? session?.accessToken : '';
|
||||
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState<boolean>(false);
|
||||
@@ -44,9 +50,15 @@ export default function SeriesSettingSidebar(
|
||||
try {
|
||||
const useLocal: boolean = isDesktop && isCurrentlyOffline();
|
||||
const deletedAt: number = Math.floor(Date.now() / 1000);
|
||||
const success: boolean = useLocal
|
||||
? await deleteSeries(seriesId, deletedAt)
|
||||
: await apiDelete<boolean>('series/delete', {seriesId: seriesId}, userToken, lang);
|
||||
let success: boolean;
|
||||
if (useLocal) {
|
||||
success = await deleteSeries(seriesId, deletedAt);
|
||||
} else {
|
||||
success = await apiDelete<boolean>('series/delete', {seriesId: seriesId}, userToken, lang);
|
||||
if (isDesktop && localSyncedSeries.find((s: SyncedSeries): boolean => s.id === seriesId)) {
|
||||
addToQueue('delete_series', {seriesId, deletedAt});
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
successMessage(t('seriesSetting.deleteSuccess'));
|
||||
onClose();
|
||||
|
||||
@@ -14,6 +14,9 @@ import {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import {SeriesContext, SeriesContextProps} from "@/context/SeriesContext";
|
||||
import {SeriesDetailResponse, SeriesUpdateResponse} from "@/lib/types/series";
|
||||
import PulseLoader from '@/components/ui/PulseLoader';
|
||||
import {SeriesSyncContext, SeriesSyncContextProps} from '@/context/SeriesSyncContext';
|
||||
import {SyncedSeries} from '@/lib/types/synced-series';
|
||||
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
|
||||
|
||||
function BasicSeriesInformation(props: object, ref: React.Ref<{ handleSave: () => Promise<void> }>) {
|
||||
const t = useTranslations();
|
||||
@@ -24,6 +27,8 @@ function BasicSeriesInformation(props: object, ref: React.Ref<{ handleSave: () =
|
||||
const userToken: string = session?.accessToken ? session?.accessToken : '';
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {isCurrentlyOffline} = useContext(OfflineContext);
|
||||
const {localSyncedSeries}: SeriesSyncContextProps = useContext<SeriesSyncContextProps>(SeriesSyncContext);
|
||||
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
||||
const useLocal: boolean = isDesktop && isCurrentlyOffline();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
@@ -69,9 +74,16 @@ function BasicSeriesInformation(props: object, ref: React.Ref<{ handleSave: () =
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response: SeriesUpdateResponse = useLocal
|
||||
? {success: await updateSeries({seriesId, name, description})} as SeriesUpdateResponse
|
||||
: await apiPut<SeriesUpdateResponse>('series/update', {seriesId, name, description}, userToken, lang);
|
||||
let response: SeriesUpdateResponse;
|
||||
const updateData = {seriesId, name, description};
|
||||
if (useLocal) {
|
||||
response = {success: await updateSeries(updateData)} as SeriesUpdateResponse;
|
||||
} else {
|
||||
response = await apiPut<SeriesUpdateResponse>('series/update', updateData, userToken, lang);
|
||||
if (isDesktop && localSyncedSeries.find((s: SyncedSeries): boolean => s.id === seriesId)) {
|
||||
addToQueue('update_series', updateData);
|
||||
}
|
||||
}
|
||||
if (!response.success) {
|
||||
errorMessage(t('seriesBasicInformation.error.update'));
|
||||
return;
|
||||
|
||||
@@ -29,7 +29,8 @@ function SeriesBooksManager(props: object, ref: React.Ref<{ handleSave: () => Pr
|
||||
const {seriesId}: SeriesContextProps = useContext<SeriesContextProps>(SeriesContext);
|
||||
const {
|
||||
serverSyncedBooks,
|
||||
setServerSyncedBooks
|
||||
setServerSyncedBooks,
|
||||
localSyncedBooks
|
||||
}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const userToken: string = session?.accessToken ? session?.accessToken : '';
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
@@ -49,11 +50,12 @@ function SeriesBooksManager(props: object, ref: React.Ref<{ handleSave: () => Pr
|
||||
|
||||
useEffect(function () {
|
||||
const booksInSeries: string[] = seriesBooks.map((book: SeriesBookProps) => book.bookId);
|
||||
const filteredBooks: SyncedBook[] = serverSyncedBooks.filter(
|
||||
const allBooks: SyncedBook[] = useLocal ? localSyncedBooks : serverSyncedBooks;
|
||||
const filteredBooks: SyncedBook[] = allBooks.filter(
|
||||
(book: SyncedBook) => !booksInSeries.includes(book.id)
|
||||
);
|
||||
setAvailableBooks(filteredBooks);
|
||||
}, [seriesBooks, serverSyncedBooks]);
|
||||
}, [seriesBooks, serverSyncedBooks, localSyncedBooks, useLocal]);
|
||||
|
||||
async function loadSeriesBooks(): Promise<void> {
|
||||
setIsLoading(true);
|
||||
@@ -97,7 +99,8 @@ function SeriesBooksManager(props: object, ref: React.Ref<{ handleSave: () => Pr
|
||||
: await apiPost<boolean>('series/book/add', {seriesId: seriesId, bookId: selectedBookToAdd}, userToken, lang);
|
||||
|
||||
if (response) {
|
||||
const addedBook: SyncedBook | undefined = serverSyncedBooks.find(
|
||||
const allBooks: SyncedBook[] = useLocal ? localSyncedBooks : serverSyncedBooks;
|
||||
const addedBook: SyncedBook | undefined = allBooks.find(
|
||||
(book: SyncedBook) => book.id === selectedBookToAdd
|
||||
);
|
||||
if (addedBook) {
|
||||
|
||||
Reference in New Issue
Block a user