From 23f1592c22b6c0c8998925987a016e4512627d29 Mon Sep 17 00:00:00 2001 From: natreex Date: Wed, 26 Nov 2025 22:52:34 -0500 Subject: [PATCH] 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. --- .../book/settings/BasicInformationSetting.tsx | 32 +++-- components/book/settings/DeleteBook.tsx | 23 ++-- .../characters/CharacterComponent.tsx | 74 ++++++++--- .../settings/characters/CharacterDetail.tsx | 15 ++- .../settings/guide-line/GuideLineSetting.tsx | 52 +++++--- .../settings/locations/LocationComponent.tsx | 116 ++++++++++++++---- components/book/settings/story/Act.tsx | 97 +++++++++++---- components/book/settings/story/Issue.tsx | 44 +++++-- .../book/settings/story/StorySetting.tsx | 32 +++-- .../book/settings/world/WorldElement.tsx | 35 ++++-- .../book/settings/world/WorldSetting.tsx | 45 +++++-- electron/ipc/book.ipc.ts | 73 +++++++---- electron/ipc/chapter.ipc.ts | 9 +- electron/ipc/character.ipc.ts | 36 ++++-- electron/ipc/location.ipc.ts | 36 ++++-- 15 files changed, 518 insertions(+), 201 deletions(-) diff --git a/components/book/settings/BasicInformationSetting.tsx b/components/book/settings/BasicInformationSetting.tsx index a85dcc6..08c04eb 100644 --- a/components/book/settings/BasicInformationSetting.tsx +++ b/components/book/settings/BasicInformationSetting.tsx @@ -16,11 +16,13 @@ import {configs} from "@/lib/configs"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; import {BookProps} from "@/lib/models/Book"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; function BasicInformationSetting(props: any, ref: any) { const t = useTranslations(); const {lang} = useContext(LangContext) - + const {isCurrentlyOffline} = useContext(OfflineContext); + const {session} = useContext(SessionContext); const {book, setBook} = useContext(BookContext); const userToken: string = session?.accessToken ? session?.accessToken : ''; @@ -114,14 +116,26 @@ function BasicInformationSetting(props: any, ref: any) { return; } try { - const response: boolean = await System.authPostToServer('book/basic-information', { - title: title, - subTitle: subTitle, - summary: summary, - publicationDate: publicationDate, - wordCount: wordCount, - bookId: bookId - }, userToken, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:updateBasicInformation', { + title: title, + subTitle: subTitle, + summary: summary, + publicationDate: publicationDate, + wordCount: wordCount, + bookId: bookId + }); + } else { + response = await System.authPostToServer('book/basic-information', { + title: title, + subTitle: subTitle, + summary: summary, + publicationDate: publicationDate, + wordCount: wordCount, + bookId: bookId + }, userToken, lang); + } if (!response) { errorMessage(t('basicInformationSetting.error.update')); return; diff --git a/components/book/settings/DeleteBook.tsx b/components/book/settings/DeleteBook.tsx index 19686fa..bf9a934 100644 --- a/components/book/settings/DeleteBook.tsx +++ b/components/book/settings/DeleteBook.tsx @@ -7,6 +7,7 @@ import {BookProps} from "@/lib/models/Book"; import {LangContext, LangContextProps} from "@/context/LangContext"; import {AlertContext, AlertContextProps} from "@/context/AlertContext"; import AlertBox from "@/components/AlertBox"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface DeleteBookProps { bookId: string; @@ -15,6 +16,7 @@ interface DeleteBookProps { export default function DeleteBook({bookId}: DeleteBookProps) { const {session, setSession} = useContext(SessionContext); const {lang} = useContext(LangContext) + const {isCurrentlyOffline} = useContext(OfflineContext); const [showConfirmBox, setShowConfirmBox] = useState(false); const {errorMessage} = useContext(AlertContext) @@ -24,14 +26,21 @@ export default function DeleteBook({bookId}: DeleteBookProps) { async function handleDeleteBook(): Promise { try { - const response: boolean = await System.authDeleteToServer( - `book/delete`, - { + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:delete', { id: bookId, - }, - session.accessToken, - lang - ); + }); + } else { + response = await System.authDeleteToServer( + `book/delete`, + { + id: bookId, + }, + session.accessToken, + lang + ); + } if (response) { setShowConfirmBox(false); const updatedBooks: BookProps[] = (session.user?.books || []).reduce((acc: BookProps[], book: BookProps): BookProps[] => { diff --git a/components/book/settings/characters/CharacterComponent.tsx b/components/book/settings/characters/CharacterComponent.tsx index fbec410..346faed 100644 --- a/components/book/settings/characters/CharacterComponent.tsx +++ b/components/book/settings/characters/CharacterComponent.tsx @@ -9,6 +9,7 @@ import {BookContext} from "@/context/BookContext"; import CharacterDetail from "@/components/book/settings/characters/CharacterDetail"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface CharacterDetailProps { selectedCharacter: CharacterProps | null; @@ -46,6 +47,7 @@ const initialCharacterState: CharacterProps = { export function CharacterComponent(props: any, ref: any) { const t = useTranslations(); const {lang} = useContext(LangContext) + const {isCurrentlyOffline} = useContext(OfflineContext); const {session} = useContext(SessionContext); const {book} = useContext(BookContext); const {errorMessage, successMessage} = useContext(AlertContext); @@ -64,9 +66,14 @@ export function CharacterComponent(props: any, ref: any) { async function getCharacters(): Promise { try { - const response: CharacterProps[] = await System.authGetQueryToServer(`character/list`, session.accessToken, lang, { - bookid: book?.bookId, - }); + let response: CharacterProps[]; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:character:list', {bookid: book?.bookId}); + } else { + response = await System.authGetQueryToServer(`character/list`, session.accessToken, lang, { + bookid: book?.bookId, + }); + } if (response) { setCharacters(response); } @@ -108,10 +115,18 @@ export function CharacterComponent(props: any, ref: any) { return; } try { - const characterId: string = await System.authPostToServer(`character/add`, { - bookId: book?.bookId, - character: updatedCharacter, - }, session.accessToken, lang); + let characterId: string; + if (isCurrentlyOffline()) { + characterId = await window.electron.invoke('db:character:create', { + bookId: book?.bookId, + character: updatedCharacter, + }); + } else { + characterId = await System.authPostToServer(`character/add`, { + bookId: book?.bookId, + character: updatedCharacter, + }, session.accessToken, lang); + } if (!characterId) { errorMessage(t("characterComponent.errorAddCharacter")); return; @@ -130,9 +145,16 @@ export function CharacterComponent(props: any, ref: any) { async function updateCharacter(updatedCharacter: CharacterProps,): Promise { try { - const response: boolean = await System.authPostToServer(`character/update`, { - character: updatedCharacter, - }, session.accessToken, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:character:update', { + character: updatedCharacter, + }); + } else { + response = await System.authPostToServer(`character/update`, { + character: updatedCharacter, + }, session.accessToken, lang); + } if (!response) { errorMessage(t("characterComponent.errorUpdateCharacter")); return; @@ -175,11 +197,20 @@ export function CharacterComponent(props: any, ref: any) { setSelectedCharacter({...selectedCharacter, [section]: updatedSection}); } else { try { - const attributeId: string = await System.authPostToServer(`character/attribute/add`, { - characterId: selectedCharacter.id, - type: section, - name: value.name, - }, session.accessToken, lang); + let attributeId: string; + if (isCurrentlyOffline()) { + attributeId = await window.electron.invoke('db:character:attribute:add', { + characterId: selectedCharacter.id, + type: section, + name: value.name, + }); + } else { + attributeId = await System.authPostToServer(`character/attribute/add`, { + characterId: selectedCharacter.id, + type: section, + name: value.name, + }, session.accessToken, lang); + } if (!attributeId) { errorMessage(t("characterComponent.errorAddAttribute")); return; @@ -214,9 +245,16 @@ export function CharacterComponent(props: any, ref: any) { setSelectedCharacter({...selectedCharacter, [section]: updatedSection}); } else { try { - const response: boolean = await System.authDeleteToServer(`character/attribute/delete`, { - attributeId: attrId, - }, session.accessToken, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:character:attribute:delete', { + attributeId: attrId, + }); + } else { + response = await System.authDeleteToServer(`character/attribute/delete`, { + attributeId: attrId, + }, session.accessToken, lang); + } if (!response) { errorMessage(t("characterComponent.errorRemoveAttribute")); return; diff --git a/components/book/settings/characters/CharacterDetail.tsx b/components/book/settings/characters/CharacterDetail.tsx index 55e1d88..1a30989 100644 --- a/components/book/settings/characters/CharacterDetail.tsx +++ b/components/book/settings/characters/CharacterDetail.tsx @@ -29,6 +29,7 @@ import {Dispatch, SetStateAction, useContext, useEffect} from "react"; import CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement"; import {useTranslations} from "next-intl"; import {LangContext} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface CharacterDetailProps { selectedCharacter: CharacterProps | null; @@ -55,6 +56,7 @@ export default function CharacterDetail( ) { const t = useTranslations(); const {lang} = useContext(LangContext); + const {isCurrentlyOffline} = useContext(OfflineContext); const {session} = useContext(SessionContext); const {errorMessage} = useContext(AlertContext); @@ -66,9 +68,16 @@ export default function CharacterDetail( async function getAttributes(): Promise { try { - const response: CharacterAttribute = await System.authGetQueryToServer(`character/attribute`, session.accessToken, lang, { - characterId: selectedCharacter?.id, - }); + let response: CharacterAttribute; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:character:attributes', { + characterId: selectedCharacter?.id, + }); + } else { + response = await System.authGetQueryToServer(`character/attribute`, session.accessToken, lang, { + characterId: selectedCharacter?.id, + }); + } if (response) { setSelectedCharacter({ id: selectedCharacter?.id ?? '', diff --git a/components/book/settings/guide-line/GuideLineSetting.tsx b/components/book/settings/guide-line/GuideLineSetting.tsx index 3472404..3e3f5a6 100644 --- a/components/book/settings/guide-line/GuideLineSetting.tsx +++ b/components/book/settings/guide-line/GuideLineSetting.tsx @@ -21,10 +21,12 @@ import { } from "@/lib/models/Story"; import {useTranslations} from "next-intl"; import {LangContext} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; function GuideLineSetting(props: any, ref: any) { const t = useTranslations(); const {lang} = useContext(LangContext); + const {isCurrentlyOffline} = useContext(OfflineContext); const {book} = useContext(BookContext); const {session} = useContext(SessionContext); const userToken: string = session?.accessToken ? session?.accessToken : ''; @@ -75,7 +77,12 @@ function GuideLineSetting(props: any, ref: any) { async function getAIGuideLine(): Promise { try { - const response: GuideLineAI = await System.authGetQueryToServer(`book/ai/guideline`, userToken, lang, {id: bookId}); + let response: GuideLineAI; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:guideline:ai:get', {id: bookId}); + } else { + response = await System.authGetQueryToServer(`book/ai/guideline`, userToken, lang, {id: bookId}); + } if (response) { setPlotSummary(response.globalResume); setVerbTense(response.verbeTense?.toString() || ''); @@ -96,13 +103,17 @@ function GuideLineSetting(props: any, ref: any) { async function getGuideLine(): Promise { try { - const response: GuideLine = - await System.authGetQueryToServer( + let response: GuideLine; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:guideline:get', {id: bookId}); + } else { + response = await System.authGetQueryToServer( `book/guide-line`, userToken, lang, {id: bookId}, ); + } if (response) { setTone(response.tone); setAtmosphere(response.atmosphere); @@ -126,25 +137,30 @@ function GuideLineSetting(props: any, ref: any) { async function savePersonal(): Promise { try { - const response: boolean = - await System.authPostToServer( + let response: boolean; + const guidelineData = { + bookId: bookId, + tone: tone, + atmosphere: atmosphere, + writingStyle: writingStyle, + themes: themes, + symbolism: symbolism, + motifs: motifs, + narrativeVoice: narrativeVoice, + pacing: pacing, + intendedAudience: intendedAudience, + keyMessages: keyMessages, + }; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:guideline:update', guidelineData); + } else { + response = await System.authPostToServer( 'book/guide-line', - { - bookId: bookId, - tone: tone, - atmosphere: atmosphere, - writingStyle: writingStyle, - themes: themes, - symbolism: symbolism, - motifs: motifs, - narrativeVoice: narrativeVoice, - pacing: pacing, - intendedAudience: intendedAudience, - keyMessages: keyMessages, - }, + guidelineData, userToken, lang, ); + } if (!response) { errorMessage(t("guideLineSetting.saveError")); return; diff --git a/components/book/settings/locations/LocationComponent.tsx b/components/book/settings/locations/LocationComponent.tsx index 8be333a..bdefec1 100644 --- a/components/book/settings/locations/LocationComponent.tsx +++ b/components/book/settings/locations/LocationComponent.tsx @@ -11,6 +11,7 @@ import TextInput from '@/components/form/TextInput'; import TexteAreaInput from "@/components/form/TexteAreaInput"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface SubElement { id: string; @@ -34,6 +35,7 @@ interface LocationProps { export function LocationComponent(props: any, ref: any) { const t = useTranslations(); const {lang} = useContext(LangContext); + const {isCurrentlyOffline} = useContext(OfflineContext); const {session} = useContext(SessionContext); const {successMessage, errorMessage} = useContext(AlertContext); const {book} = useContext(BookContext); @@ -58,9 +60,14 @@ export function LocationComponent(props: any, ref: any) { async function getAllLocations(): Promise { try { - const response: LocationProps[] = await System.authGetQueryToServer(`location/all`, token, lang, { - bookid: bookId, - }); + let response: LocationProps[]; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:location:all', {bookid: bookId}); + } else { + response = await System.authGetQueryToServer(`location/all`, token, lang, { + bookid: bookId, + }); + } if (response && response.length > 0) { setSections(response); } @@ -79,10 +86,18 @@ export function LocationComponent(props: any, ref: any) { return } try { - const sectionId: string = await System.authPostToServer(`location/section/add`, { - bookId: bookId, - locationName: newSectionName, - }, token, lang); + let sectionId: string; + if (isCurrentlyOffline()) { + sectionId = await window.electron.invoke('db:location:section:add', { + bookId: bookId, + locationName: newSectionName, + }); + } else { + sectionId = await System.authPostToServer(`location/section/add`, { + bookId: bookId, + locationName: newSectionName, + }, token, lang); + } if (!sectionId) { errorMessage(t('locationComponent.errorUnknownAddSection')); return; @@ -109,12 +124,21 @@ export function LocationComponent(props: any, ref: any) { return } try { - const elementId: string = await System.authPostToServer(`location/element/add`, { + let elementId: string; + if (isCurrentlyOffline()) { + elementId = await window.electron.invoke('db:location:element:add', { bookId: bookId, locationId: sectionId, elementName: newElementNames[sectionId], - }, - token, lang); + }); + } else { + elementId = await System.authPostToServer(`location/element/add`, { + bookId: bookId, + locationId: sectionId, + elementName: newElementNames[sectionId], + }, + token, lang); + } if (!elementId) { errorMessage(t('locationComponent.errorUnknownAddElement')); return; @@ -167,10 +191,18 @@ export function LocationComponent(props: any, ref: any) { (section: LocationProps): boolean => section.id === sectionId, ); try { - const subElementId: string = await System.authPostToServer(`location/sub-element/add`, { - elementId: sections[sectionIndex].elements[elementIndex].id, - subElementName: newSubElementNames[elementIndex], - }, token, lang); + let subElementId: string; + if (isCurrentlyOffline()) { + subElementId = await window.electron.invoke('db:location:subelement:add', { + elementId: sections[sectionIndex].elements[elementIndex].id, + subElementName: newSubElementNames[elementIndex], + }); + } else { + subElementId = await System.authPostToServer(`location/sub-element/add`, { + elementId: sections[sectionIndex].elements[elementIndex].id, + subElementName: newSubElementNames[elementIndex], + }, token, lang); + } if (!subElementId) { errorMessage(t('locationComponent.errorUnknownAddSubElement')); return; @@ -214,10 +246,18 @@ export function LocationComponent(props: any, ref: any) { elementIndex: number, ): Promise { try { - const response: boolean = await System.authDeleteToServer(`location/element/delete`, { - elementId: sections.find((section: LocationProps): boolean => section.id === sectionId) - ?.elements[elementIndex].id, - }, token, lang); + let response: boolean; + const elementId = sections.find((section: LocationProps): boolean => section.id === sectionId) + ?.elements[elementIndex].id; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:location:element:delete', { + elementId: elementId, + }); + } else { + response = await System.authDeleteToServer(`location/element/delete`, { + elementId: elementId, + }, token, lang); + } if (!response) { errorMessage(t('locationComponent.errorUnknownDeleteElement')); return; @@ -241,9 +281,17 @@ export function LocationComponent(props: any, ref: any) { subElementIndex: number, ): Promise { try { - const response: boolean = await System.authDeleteToServer(`location/sub-element/delete`, { - subElementId: sections.find((section: LocationProps): boolean => section.id === sectionId)?.elements[elementIndex].subElements[subElementIndex].id, - }, token, lang); + let response: boolean; + const subElementId = sections.find((section: LocationProps): boolean => section.id === sectionId)?.elements[elementIndex].subElements[subElementIndex].id; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:location:subelement:delete', { + subElementId: subElementId, + }); + } else { + response = await System.authDeleteToServer(`location/sub-element/delete`, { + subElementId: subElementId, + }, token, lang); + } if (!response) { errorMessage(t('locationComponent.errorUnknownDeleteSubElement')); return; @@ -263,9 +311,16 @@ export function LocationComponent(props: any, ref: any) { async function handleRemoveSection(sectionId: string): Promise { try { - const response: boolean = await System.authDeleteToServer(`location/delete`, { - locationId: sectionId, - }, token, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:location:delete', { + locationId: sectionId, + }); + } else { + response = await System.authDeleteToServer(`location/delete`, { + locationId: sectionId, + }, token, lang); + } if (!response) { errorMessage(t('locationComponent.errorUnknownDeleteSection')); return; @@ -283,9 +338,16 @@ export function LocationComponent(props: any, ref: any) { async function handleSave(): Promise { try { - const response: boolean = await System.authPostToServer(`location/update`, { - locations: sections, - }, token, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:location:update', { + locations: sections, + }); + } else { + response = await System.authPostToServer(`location/update`, { + locations: sections, + }, token, lang); + } if (!response) { errorMessage(t('locationComponent.errorUnknownSave')); return; diff --git a/components/book/settings/story/Act.tsx b/components/book/settings/story/Act.tsx index 1331353..70624d3 100644 --- a/components/book/settings/story/Act.tsx +++ b/components/book/settings/story/Act.tsx @@ -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(LangContext); + const {isCurrentlyOffline} = useContext(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 { if (newIncidentTitle.trim() === '') return; - + try { - const incidentId: string = - await System.authPostToServer('book/incident/new', { + let incidentId: string; + if (isCurrentlyOffline()) { + incidentId = await window.electron.invoke('db:book:incident:add', { + bookId, + name: newIncidentTitle, + }); + } else { + incidentId = await System.authPostToServer('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 { try { - const response: boolean = await System.authDeleteToServer('book/incident/remove', { - bookId, - incidentId, - }, token, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:incident:remove', { + bookId, + incidentId, + }); + } else { + response = await System.authDeleteToServer('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 { if (newPlotPointTitle.trim() === '') return; try { - const plotId: string = await System.authPostToServer('book/plot/new', { - bookId, - name: newPlotPointTitle, - incidentId: selectedIncidentId, - }, token, lang); + let plotId: string; + if (isCurrentlyOffline()) { + plotId = await window.electron.invoke('db:book:plot:add', { + bookId, + name: newPlotPointTitle, + incidentId: selectedIncidentId, + }); + } else { + plotId = await System.authPostToServer('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 { try { - const response: boolean = await System.authDeleteToServer('book/plot/remove', { - plotId: plotPointId, - }, token, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:plot:remove', { + plotId: plotPointId, + }); + } else { + response = await System.authDeleteToServer('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('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('db:chapter:information:add', linkData); + } else { + linkId = await System.authPostToServer('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 { try { - const response: boolean = await System.authDeleteToServer('chapter/resume/remove', { - chapterInfoId, - }, token, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:chapter:information:remove', { + chapterInfoId, + }); + } else { + response = await System.authDeleteToServer('chapter/resume/remove', { + chapterInfoId, + }, token, lang); + } if (!response) { errorMessage(t('errorUnlinkChapter')); return; diff --git a/components/book/settings/story/Issue.tsx b/components/book/settings/story/Issue.tsx index ecb40c7..393a7a1 100644 --- a/components/book/settings/story/Issue.tsx +++ b/components/book/settings/story/Issue.tsx @@ -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(LangContext); + const {isCurrentlyOffline} = useContext(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('book/issue/add', { - bookId, - name: newIssueName, - }, token, lang); + let issueId: string; + if (isCurrentlyOffline()) { + issueId = await window.electron.invoke('db:book:issue:add', { + bookId, + name: newIssueName, + }); + } else { + issueId = await System.authPostToServer('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( - 'book/issue/remove', - { + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:issue:remove', { bookId, issueId, - }, - token, - lang - ); + }); + } else { + response = await System.authDeleteToServer( + 'book/issue/remove', + { + bookId, + issueId, + }, + token, + lang + ); + } if (response) { const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,); setIssues(updatedIssues); diff --git a/components/book/settings/story/StorySetting.tsx b/components/book/settings/story/StorySetting.tsx index ea4310f..144780a 100644 --- a/components/book/settings/story/StorySetting.tsx +++ b/components/book/settings/story/StorySetting.tsx @@ -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(LangContext); + const {isCurrentlyOffline} = useContext(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 { try { - const response: StoryFetchData = await System.authGetQueryToServer(`book/story`, userToken, lang, { - bookid: bookId, - }); + let response: StoryFetchData; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:story:get', {bookid: bookId}); + } else { + response = await System.authGetQueryToServer(`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 { try { - const response: boolean = - await System.authPostToServer('book/story', { - bookId, - acts, - mainChapters, - issues, - }, userToken, lang); + let response: boolean; + const storyData = { + bookId, + acts, + mainChapters, + issues, + }; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:story:update', storyData); + } else { + response = await System.authPostToServer('book/story', storyData, userToken, lang); + } if (!response) { errorMessage(t("story.errorSave")) } diff --git a/components/book/settings/world/WorldElement.tsx b/components/book/settings/world/WorldElement.tsx index bf4f0ed..10e3c2f 100644 --- a/components/book/settings/world/WorldElement.tsx +++ b/components/book/settings/world/WorldElement.tsx @@ -11,6 +11,7 @@ import System from "@/lib/models/System"; import InputField from "@/components/form/InputField"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface WorldElementInputProps { sectionLabel: string; @@ -20,6 +21,7 @@ interface WorldElementInputProps { export default function WorldElementComponent({sectionLabel, sectionType}: WorldElementInputProps) { const t = useTranslations(); const {lang} = useContext(LangContext); + const {isCurrentlyOffline} = useContext(OfflineContext); const {worlds, setWorlds, selectedWorldIndex} = useContext(WorldContext); const {errorMessage, successMessage} = useContext(AlertContext); const {session} = useContext(SessionContext); @@ -31,9 +33,17 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World index: number, ): Promise { try { - const response: boolean = await System.authDeleteToServer('book/world/element/delete', { - elementId: (worlds[selectedWorldIndex][section] as WorldElement[])[index].id, - }, session.accessToken, lang); + let response: boolean; + const elementId = (worlds[selectedWorldIndex][section] as WorldElement[])[index].id; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:world:element:remove', { + elementId: elementId, + }); + } else { + response = await System.authDeleteToServer('book/world/element/delete', { + elementId: elementId, + }, session.accessToken, lang); + } if (!response) { errorMessage(t("worldSetting.unknownError")) } @@ -58,11 +68,20 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World return; } try { - const elementId: string = await System.authPostToServer('book/world/element/add', { - elementType: section, - worldId: worlds[selectedWorldIndex].id, - elementName: newElementName, - }, session.accessToken, lang); + let elementId: string; + if (isCurrentlyOffline()) { + elementId = await window.electron.invoke('db:book:world:element:add', { + elementType: section, + worldId: worlds[selectedWorldIndex].id, + elementName: newElementName, + }); + } else { + elementId = await System.authPostToServer('book/world/element/add', { + elementType: section, + worldId: worlds[selectedWorldIndex].id, + elementName: newElementName, + }, session.accessToken, lang); + } if (!elementId) { errorMessage(t("worldSetting.unknownError")) return; diff --git a/components/book/settings/world/WorldSetting.tsx b/components/book/settings/world/WorldSetting.tsx index 987e1d4..06d3839 100644 --- a/components/book/settings/world/WorldSetting.tsx +++ b/components/book/settings/world/WorldSetting.tsx @@ -16,6 +16,7 @@ import WorldElementComponent from './WorldElement'; import SelectBox from "@/components/form/SelectBox"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; +import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; export interface ElementSection { title: string; @@ -26,6 +27,7 @@ export interface ElementSection { export function WorldSetting(props: any, ref: any) { const t = useTranslations(); const {lang} = useContext(LangContext); + const {isCurrentlyOffline} = useContext(OfflineContext); const {errorMessage, successMessage} = useContext(AlertContext); const {session} = useContext(SessionContext); const {book} = useContext(BookContext); @@ -49,9 +51,14 @@ export function WorldSetting(props: any, ref: any) { async function getWorlds() { try { - const response: WorldProps[] = await System.authGetQueryToServer(`book/worlds`, session.accessToken, lang, { - bookid: bookId, - }); + let response: WorldProps[]; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:worlds:get', {bookid: bookId}); + } else { + response = await System.authGetQueryToServer(`book/worlds`, session.accessToken, lang, { + bookid: bookId, + }); + } if (response) { setWorlds(response); const formattedWorlds: SelectBoxProps[] = response.map( @@ -77,10 +84,18 @@ export function WorldSetting(props: any, ref: any) { return; } try { - const worldId: string = await System.authPostToServer('book/world/add', { - worldName: newWorldName, - bookId: bookId, - }, session.accessToken, lang); + let worldId: string; + if (isCurrentlyOffline()) { + worldId = await window.electron.invoke('db:book:world:add', { + worldName: newWorldName, + bookId: bookId, + }); + } else { + worldId = await System.authPostToServer('book/world/add', { + worldName: newWorldName, + bookId: bookId, + }, session.accessToken, lang); + } if (!worldId) { errorMessage(t("worldSetting.addWorldError")); return; @@ -125,10 +140,18 @@ export function WorldSetting(props: any, ref: any) { async function handleUpdateWorld(): Promise { try { - const response: boolean = await System.authPutToServer('book/world/update', { - world: worlds[selectedWorldIndex], - bookId: bookId, - }, session.accessToken, lang); + let response: boolean; + if (isCurrentlyOffline()) { + response = await window.electron.invoke('db:book:world:update', { + world: worlds[selectedWorldIndex], + bookId: bookId, + }); + } else { + response = await System.authPutToServer('book/world/update', { + world: worlds[selectedWorldIndex], + bookId: bookId, + }, session.accessToken, lang); + } if (!response) { errorMessage(t("worldSetting.updateWorldError")); return; diff --git a/electron/ipc/book.ipc.ts b/electron/ipc/book.ipc.ts index 232583b..c39e133 100644 --- a/electron/ipc/book.ipc.ts +++ b/electron/ipc/book.ipc.ts @@ -111,10 +111,13 @@ ipcMain.handle('db:book:updateBasicInformation', createHandler(async function(userId: string, bookId: string, lang: 'fr' | 'en') { - return await Book.getGuideLine(userId, bookId, lang); + createHandler(async function(userId: string, data: GetGuidelineData, lang: 'fr' | 'en') { + return await Book.getGuideLine(userId, data.id, lang); } ) ); @@ -142,10 +145,13 @@ ipcMain.handle('db:book:guideline:update', createHandler( - async function(userId: string, bookId: string, lang: 'fr' | 'en'):Promise { - const acts:Act[] = await Book.getActsData(userId, bookId, lang); - const issues:Issue[] = await Book.getIssuesFromBook(userId, bookId, lang); +interface GetStoryData { + bookid: string; +} +ipcMain.handle('db:book:story:get', createHandler( + async function(userId: string, data: GetStoryData, lang: 'fr' | 'en'):Promise { + const acts:Act[] = await Book.getActsData(userId, data.bookid, lang); + const issues:Issue[] = await Book.getIssuesFromBook(userId, data.bookid, lang); return { acts, issues @@ -220,11 +226,14 @@ ipcMain.handle('db:book:plot:add', createHandler( ); // DELETE /book/plot/remove - Remove plot point +interface RemovePlotData { + plotId: string; +} ipcMain.handle( 'db:book:plot:remove', - createHandler( - function(userId: string, plotPointId: string, lang: 'fr' | 'en') { - return Book.removePlotPoint(userId, plotPointId, lang); + createHandler( + function(userId: string, data: RemovePlotData, lang: 'fr' | 'en') { + return Book.removePlotPoint(userId, data.plotId, lang); } ) ); @@ -238,17 +247,24 @@ ipcMain.handle('db:book:issue:add', createHandler( ); // DELETE /book/issue/remove - Remove issue -ipcMain.handle('db:book:issue:remove', createHandler( - function(userId: string, issueId: string, lang: 'fr' | 'en') { - return Book.removeIssue(userId, issueId, lang); +interface RemoveIssueData { + bookId: string; + issueId: string; +} +ipcMain.handle('db:book:issue:remove', createHandler( + function(userId: string, data: RemoveIssueData, lang: 'fr' | 'en') { + return Book.removeIssue(userId, data.issueId, lang); } ) ); // GET /book/worlds - Get worlds for book -ipcMain.handle('db:book:worlds:get', createHandler( - function(userId: string, bookId: string, lang: 'fr' | 'en') { - return Book.getWorlds(userId, bookId, lang); +interface GetWorldsData { + bookid: string; +} +ipcMain.handle('db:book:worlds:get', createHandler( + function(userId: string, data: GetWorldsData, lang: 'fr' | 'en') { + return Book.getWorlds(userId, data.bookid, lang); } ) ); @@ -275,25 +291,34 @@ ipcMain.handle('db:book:world:element:add', createHandler( - function(userId: string, elementId: string, lang: 'fr' | 'en') { - return Book.removeElementFromWorld(userId, elementId, lang); +interface RemoveWorldElementData { + elementId: string; +} +ipcMain.handle('db:book:world:element:remove', createHandler( + function(userId: string, data: RemoveWorldElementData, lang: 'fr' | 'en') { + return Book.removeElementFromWorld(userId, data.elementId, lang); } ) ); // DELETE /book/delete - Delete book -ipcMain.handle('db:book:delete', createHandler( - function(userId: string, bookId: string, lang: 'fr' | 'en') { - return Book.removeBook(userId, bookId, lang); +interface DeleteBookData { + id: string; +} +ipcMain.handle('db:book:delete', createHandler( + function(userId: string, data: DeleteBookData, lang: 'fr' | 'en') { + return Book.removeBook(userId, data.id, lang); } ) ); // GET /book/ai/guideline - Get AI guideline -ipcMain.handle('db:book:guideline:ai:get', createHandler( - function(userId: string, bookId: string, lang: 'fr' | 'en') { - return Book.getGuideLineAI(userId, bookId, lang); +interface GetAIGuidelineData { + id: string; +} +ipcMain.handle('db:book:guideline:ai:get', createHandler( + function(userId: string, data: GetAIGuidelineData, lang: 'fr' | 'en') { + return Book.getGuideLineAI(userId, data.id, lang); } ) ); diff --git a/electron/ipc/chapter.ipc.ts b/electron/ipc/chapter.ipc.ts index f7e8aaa..91d27f6 100644 --- a/electron/ipc/chapter.ipc.ts +++ b/electron/ipc/chapter.ipc.ts @@ -148,9 +148,12 @@ ipcMain.handle('db:chapter:information:add', createHandler( - function(userId: string, chapterInfoId: string, lang: 'fr' | 'en'): boolean { - return Chapter.removeChapterInformation(userId, chapterInfoId, lang); +interface RemoveChapterInfoData { + chapterInfoId: string; +} +ipcMain.handle('db:chapter:information:remove', createHandler( + function(userId: string, data: RemoveChapterInfoData, lang: 'fr' | 'en'): boolean { + return Chapter.removeChapterInformation(userId, data.chapterInfoId, lang); } ) ); diff --git a/electron/ipc/character.ipc.ts b/electron/ipc/character.ipc.ts index 95ae5c6..496c68b 100644 --- a/electron/ipc/character.ipc.ts +++ b/electron/ipc/character.ipc.ts @@ -15,17 +15,23 @@ interface AddAttributeData { } // GET /character/list - Get character list -ipcMain.handle('db:character:list', createHandler( - function(userId: string, bookId: string, lang: 'fr' | 'en'): CharacterProps[] { - return Character.getCharacterList(userId, bookId, lang); +interface GetCharacterListData { + bookid: string; +} +ipcMain.handle('db:character:list', createHandler( + function(userId: string, data: GetCharacterListData, lang: 'fr' | 'en'): CharacterProps[] { + return Character.getCharacterList(userId, data.bookid, lang); } ) ); // GET /character/attribute - Get character attributes -ipcMain.handle('db:character:attributes', createHandler( - function(userId: string, characterId: string, lang: 'fr' | 'en'): CharacterAttribute[] { - return Character.getAttributes(characterId, userId, lang); +interface GetCharacterAttributesData { + characterId: string; +} +ipcMain.handle('db:character:attributes', createHandler( + function(userId: string, data: GetCharacterAttributesData, lang: 'fr' | 'en'): CharacterAttribute[] { + return Character.getAttributes(data.characterId, userId, lang); } ) ); @@ -47,17 +53,23 @@ ipcMain.handle('db:character:attribute:add', createHandler( - function(userId: string, attributeId: string, lang: 'fr' | 'en'): boolean { - return Character.deleteAttribute(userId, attributeId, lang); +interface DeleteAttributeData { + attributeId: string; +} +ipcMain.handle('db:character:attribute:delete', createHandler( + function(userId: string, data: DeleteAttributeData, lang: 'fr' | 'en'): boolean { + return Character.deleteAttribute(userId, data.attributeId, lang); } ) ); // POST /character/update - Update character -ipcMain.handle('db:character:update', createHandler( - function(userId: string, character: CharacterPropsPost, lang: 'fr' | 'en'): boolean { - return Character.updateCharacter(userId, character, lang); +interface UpdateCharacterData { + character: CharacterPropsPost; +} +ipcMain.handle('db:character:update', createHandler( + function(userId: string, data: UpdateCharacterData, lang: 'fr' | 'en'): boolean { + return Character.updateCharacter(userId, data.character, lang); } ) ); \ No newline at end of file diff --git a/electron/ipc/location.ipc.ts b/electron/ipc/location.ipc.ts index 8b620d7..ef18a6e 100644 --- a/electron/ipc/location.ipc.ts +++ b/electron/ipc/location.ipc.ts @@ -28,9 +28,12 @@ interface UpdateLocationData { } // GET /location/all - Get all locations -ipcMain.handle('db:location:all', createHandler( - function(userId: string, bookId: string, lang: 'fr' | 'en'): LocationProps[] { - return Location.getAllLocations(userId, bookId, lang); +interface GetAllLocationsData { + bookid: string; +} +ipcMain.handle('db:location:all', createHandler( + function(userId: string, data: GetAllLocationsData, lang: 'fr' | 'en'): LocationProps[] { + return Location.getAllLocations(userId, data.bookid, lang); } ) ); @@ -68,25 +71,34 @@ ipcMain.handle('db:location:update', createHandler( - function(userId: string, locationId: string, lang: 'fr' | 'en'): boolean { - return Location.deleteLocationSection(userId, locationId, lang); +interface DeleteLocationData { + locationId: string; +} +ipcMain.handle('db:location:delete', createHandler( + function(userId: string, data: DeleteLocationData, lang: 'fr' | 'en'): boolean { + return Location.deleteLocationSection(userId, data.locationId, lang); } ) ); // DELETE /location/element/delete - Delete location element -ipcMain.handle('db:location:element:delete', createHandler( - function(userId: string, elementId: string, lang: 'fr' | 'en'): boolean { - return Location.deleteLocationElement(userId, elementId, lang); +interface DeleteLocationElementData { + elementId: string; +} +ipcMain.handle('db:location:element:delete', createHandler( + function(userId: string, data: DeleteLocationElementData, lang: 'fr' | 'en'): boolean { + return Location.deleteLocationElement(userId, data.elementId, lang); } ) ); // DELETE /location/sub-element/delete - Delete location sub-element -ipcMain.handle('db:location:subelement:delete', createHandler( - function(userId: string, subElementId: string, lang: 'fr' | 'en'): boolean { - return Location.deleteLocationSubElement(userId, subElementId, lang); +interface DeleteLocationSubElementData { + subElementId: string; +} +ipcMain.handle('db:location:subelement:delete', createHandler( + function(userId: string, data: DeleteLocationSubElementData, lang: 'fr' | 'en'): boolean { + return Location.deleteLocationSubElement(userId, data.subElementId, lang); } ) );