Add offline mode logic for book, story, and world operations

- Integrate `OfflineContext` into book, story settings, and related components.
- Add conditional logic to toggle between server API requests and offline IPC handlers (`db:book:delete`, `db:book:story:get`, `db:location:all`, etc.).
- Refactor and update IPC handlers to accept structured data arguments for improved consistency (`data: object`).
- Ensure stricter typings in IPC handlers and frontend functions.
- Improve error handling and user feedback in both online and offline modes.
This commit is contained in:
natreex
2025-11-26 22:52:34 -05:00
parent e1abcd9490
commit 23f1592c22
15 changed files with 518 additions and 201 deletions

View File

@@ -20,6 +20,7 @@ import ActIncidents from '@/components/book/settings/story/act/ActIncidents';
import ActPlotPoints from '@/components/book/settings/story/act/ActPlotPoints';
import {useTranslations} from 'next-intl';
import {LangContext, LangContextProps} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
interface ActProps {
acts: ActType[];
@@ -30,6 +31,7 @@ interface ActProps {
export default function Act({acts, setActs, mainChapters}: ActProps) {
const t = useTranslations('actComponent');
const {lang} = useContext<LangContextProps>(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const {session} = useContext(SessionContext);
const {errorMessage, successMessage} = useContext(AlertContext);
@@ -69,13 +71,20 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
async function addIncident(actId: number): Promise<void> {
if (newIncidentTitle.trim() === '') return;
try {
const incidentId: string =
await System.authPostToServer<string>('book/incident/new', {
let incidentId: string;
if (isCurrentlyOffline()) {
incidentId = await window.electron.invoke<string>('db:book:incident:add', {
bookId,
name: newIncidentTitle,
});
} else {
incidentId = await System.authPostToServer<string>('book/incident/new', {
bookId,
name: newIncidentTitle,
}, token, lang);
}
if (!incidentId) {
errorMessage(t('errorAddIncident'));
return;
@@ -109,10 +118,18 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
async function deleteIncident(actId: number, incidentId: string): Promise<void> {
try {
const response: boolean = await System.authDeleteToServer<boolean>('book/incident/remove', {
bookId,
incidentId,
}, token, lang);
let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:incident:remove', {
bookId,
incidentId,
});
} else {
response = await System.authDeleteToServer<boolean>('book/incident/remove', {
bookId,
incidentId,
}, token, lang);
}
if (!response) {
errorMessage(t('errorDeleteIncident'));
return;
@@ -141,11 +158,20 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
async function addPlotPoint(actId: number): Promise<void> {
if (newPlotPointTitle.trim() === '') return;
try {
const plotId: string = await System.authPostToServer<string>('book/plot/new', {
bookId,
name: newPlotPointTitle,
incidentId: selectedIncidentId,
}, token, lang);
let plotId: string;
if (isCurrentlyOffline()) {
plotId = await window.electron.invoke<string>('db:book:plot:add', {
bookId,
name: newPlotPointTitle,
incidentId: selectedIncidentId,
});
} else {
plotId = await System.authPostToServer<string>('book/plot/new', {
bookId,
name: newPlotPointTitle,
incidentId: selectedIncidentId,
}, token, lang);
}
if (!plotId) {
errorMessage(t('errorAddPlotPoint'));
return;
@@ -180,9 +206,16 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
async function deletePlotPoint(actId: number, plotPointId: string): Promise<void> {
try {
const response: boolean = await System.authDeleteToServer<boolean>('book/plot/remove', {
plotId: plotPointId,
}, token, lang);
let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:plot:remove', {
plotId: plotPointId,
});
} else {
response = await System.authDeleteToServer<boolean>('book/plot/remove', {
plotId: plotPointId,
}, token, lang);
}
if (!response) {
errorMessage(t('errorDeletePlotPoint'));
return;
@@ -220,14 +253,19 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
return;
}
try {
const linkId: string =
await System.authPostToServer<string>('chapter/resume/add', {
bookId,
chapterId: chapterId,
actId: actId,
plotId: destination === 'plotPoint' ? itemId : null,
incidentId: destination === 'incident' ? itemId : null,
}, token, lang);
let linkId: string;
const linkData = {
bookId,
chapterId: chapterId,
actId: actId,
plotId: destination === 'plotPoint' ? itemId : null,
incidentId: destination === 'incident' ? itemId : null,
};
if (isCurrentlyOffline()) {
linkId = await window.electron.invoke<string>('db:chapter:information:add', linkData);
} else {
linkId = await System.authPostToServer<string>('chapter/resume/add', linkData, token, lang);
}
if (!linkId) {
errorMessage(t('errorLinkChapter'));
return;
@@ -302,9 +340,16 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
itemId?: string,
): Promise<void> {
try {
const response: boolean = await System.authDeleteToServer<boolean>('chapter/resume/remove', {
chapterInfoId,
}, token, lang);
let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:chapter:information:remove', {
chapterInfoId,
});
} else {
response = await System.authDeleteToServer<boolean>('chapter/resume/remove', {
chapterInfoId,
}, token, lang);
}
if (!response) {
errorMessage(t('errorUnlinkChapter'));
return;

View File

@@ -9,6 +9,7 @@ import {AlertContext} from '@/context/AlertContext';
import CollapsableArea from "@/components/CollapsableArea";
import {useTranslations} from "next-intl";
import {LangContext, LangContextProps} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
interface IssuesProps {
issues: Issue[];
@@ -18,6 +19,7 @@ interface IssuesProps {
export default function Issues({issues, setIssues}: IssuesProps) {
const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const {session} = useContext(SessionContext);
const {errorMessage} = useContext(AlertContext);
@@ -33,10 +35,18 @@ export default function Issues({issues, setIssues}: IssuesProps) {
return;
}
try {
const issueId: string = await System.authPostToServer<string>('book/issue/add', {
bookId,
name: newIssueName,
}, token, lang);
let issueId: string;
if (isCurrentlyOffline()) {
issueId = await window.electron.invoke<string>('db:book:issue:add', {
bookId,
name: newIssueName,
});
} else {
issueId = await System.authPostToServer<string>('book/issue/add', {
bookId,
name: newIssueName,
}, token, lang);
}
if (!issueId) {
errorMessage(t("issues.errorAdd"));
return;
@@ -61,18 +71,26 @@ export default function Issues({issues, setIssues}: IssuesProps) {
if (issueId === undefined) {
errorMessage(t("issues.errorInvalidId"));
}
try {
const response: boolean = await System.authDeleteToServer<boolean>(
'book/issue/remove',
{
let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:issue:remove', {
bookId,
issueId,
},
token,
lang
);
});
} else {
response = await System.authDeleteToServer<boolean>(
'book/issue/remove',
{
bookId,
issueId,
},
token,
lang
);
}
if (response) {
const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,);
setIssues(updatedIssues);

View File

@@ -12,6 +12,7 @@ import Issues from "@/components/book/settings/story/Issue";
import Act from "@/components/book/settings/story/Act";
import {useTranslations} from "next-intl";
import {LangContext, LangContextProps} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
export const StoryContext = createContext<{
acts: ActType[];
@@ -41,6 +42,7 @@ interface StoryFetchData {
export function Story(props: any, ref: any) {
const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const bookId: string = book?.bookId ? book.bookId.toString() : '';
const {session} = useContext(SessionContext);
@@ -68,9 +70,14 @@ export function Story(props: any, ref: any) {
async function getStoryData(): Promise<void> {
try {
const response: StoryFetchData = await System.authGetQueryToServer<StoryFetchData>(`book/story`, userToken, lang, {
bookid: bookId,
});
let response: StoryFetchData;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<StoryFetchData>('db:book:story:get', {bookid: bookId});
} else {
response = await System.authGetQueryToServer<StoryFetchData>(`book/story`, userToken, lang, {
bookid: bookId,
});
}
if (response) {
setActs(response.acts);
setMainChapters(response.mainChapter);
@@ -119,13 +126,18 @@ export function Story(props: any, ref: any) {
async function handleSave(): Promise<void> {
try {
const response: boolean =
await System.authPostToServer<boolean>('book/story', {
bookId,
acts,
mainChapters,
issues,
}, userToken, lang);
let response: boolean;
const storyData = {
bookId,
acts,
mainChapters,
issues,
};
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:story:update', storyData);
} else {
response = await System.authPostToServer<boolean>('book/story', storyData, userToken, lang);
}
if (!response) {
errorMessage(t("story.errorSave"))
}