Refactor character, chapter, and story components to support offline mode

- Add `OfflineContext` and `BookContext` to components for offline state management.
- Introduce conditional logic to toggle between server API requests and offline IPC handlers for CRUD operations.
- Refine `TextEditor`, `DraftCompanion`, and other components to disable actions or features unavailable in offline mode.
- Improve error handling and user feedback in both online and offline scenarios.
This commit is contained in:
natreex
2025-12-19 15:42:35 -05:00
parent 43c7ef375c
commit ff530f3442
16 changed files with 454 additions and 157 deletions

View File

@@ -474,9 +474,17 @@ function ScribeContent() {
return; return;
} }
response = await window.electron.invoke('db:chapter:last', currentBook?.bookId) response = await window.electron.invoke('db:chapter:last', currentBook?.bookId)
} else {
if (currentBook?.localBook) {
if (!offlineMode.isDatabaseInitialized) {
setCurrentChapter(undefined);
return;
}
response = await window.electron.invoke('db:chapter:last', currentBook?.bookId)
} else { } else {
response = await System.authGetQueryToServer<ChapterProps | null>(`chapter/last-chapter`, session.accessToken, locale, {bookid: currentBook?.bookId}); response = await System.authGetQueryToServer<ChapterProps | null>(`chapter/last-chapter`, session.accessToken, locale, {bookid: currentBook?.bookId});
} }
}
if (response) { if (response) {
setCurrentChapter(response) setCurrentChapter(response)
} else { } else {

View File

@@ -126,6 +126,16 @@ function BasicInformationSetting(props: any, ref: any) {
wordCount: wordCount, wordCount: wordCount,
bookId: bookId bookId: bookId
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:updateBasicInformation', {
title: title,
subTitle: subTitle,
summary: summary,
publicationDate: publicationDate,
wordCount: wordCount,
bookId: bookId
});
} else { } else {
response = await System.authPostToServer<boolean>('book/basic-information', { response = await System.authPostToServer<boolean>('book/basic-information', {
title: title, title: title,
@@ -136,6 +146,7 @@ function BasicInformationSetting(props: any, ref: any) {
bookId: bookId bookId: bookId
}, userToken, lang); }, userToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('basicInformationSetting.error.update')); errorMessage(t('basicInformationSetting.error.update'));
return; return;

View File

@@ -8,6 +8,7 @@ import {LangContext, LangContextProps} from "@/context/LangContext";
import {AlertContext, AlertContextProps} from "@/context/AlertContext"; import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import AlertBox from "@/components/AlertBox"; import AlertBox from "@/components/AlertBox";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {BookContext} from "@/context/BookContext";
interface DeleteBookProps { interface DeleteBookProps {
bookId: string; bookId: string;
@@ -17,6 +18,7 @@ export default function DeleteBook({bookId}: DeleteBookProps) {
const {session, setSession} = useContext(SessionContext); const {session, setSession} = useContext(SessionContext);
const {lang} = useContext<LangContextProps>(LangContext) const {lang} = useContext<LangContextProps>(LangContext)
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext); const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const [showConfirmBox, setShowConfirmBox] = useState<boolean>(false); const [showConfirmBox, setShowConfirmBox] = useState<boolean>(false);
const {errorMessage} = useContext<AlertContextProps>(AlertContext) const {errorMessage} = useContext<AlertContextProps>(AlertContext)
@@ -31,6 +33,11 @@ export default function DeleteBook({bookId}: DeleteBookProps) {
response = await window.electron.invoke<boolean>('db:book:delete', { response = await window.electron.invoke<boolean>('db:book:delete', {
id: bookId, id: bookId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:delete', {
id: bookId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>( response = await System.authDeleteToServer<boolean>(
`book/delete`, `book/delete`,
@@ -41,6 +48,7 @@ export default function DeleteBook({bookId}: DeleteBookProps) {
lang lang
); );
} }
}
if (response) { if (response) {
setShowConfirmBox(false); setShowConfirmBox(false);
const updatedBooks: BookProps[] = (session.user?.books || []).reduce((acc: BookProps[], book: BookProps): BookProps[] => { const updatedBooks: BookProps[] = (session.user?.books || []).reduce((acc: BookProps[], book: BookProps): BookProps[] => {

View File

@@ -69,11 +69,15 @@ export function CharacterComponent(props: any, ref: any) {
let response: CharacterProps[]; let response: CharacterProps[];
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<CharacterProps[]>('db:character:list', {bookid: book?.bookId}); response = await window.electron.invoke<CharacterProps[]>('db:character:list', {bookid: book?.bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<CharacterProps[]>('db:character:list', {bookid: book?.bookId});
} else { } else {
response = await System.authGetQueryToServer<CharacterProps[]>(`character/list`, session.accessToken, lang, { response = await System.authGetQueryToServer<CharacterProps[]>(`character/list`, session.accessToken, lang, {
bookid: book?.bookId, bookid: book?.bookId,
}); });
} }
}
if (response) { if (response) {
setCharacters(response); setCharacters(response);
} }
@@ -121,12 +125,19 @@ export function CharacterComponent(props: any, ref: any) {
bookId: book?.bookId, bookId: book?.bookId,
character: updatedCharacter, character: updatedCharacter,
}); });
} else {
if (book?.localBook) {
characterId = await window.electron.invoke<string>('db:character:create', {
bookId: book?.bookId,
character: updatedCharacter,
});
} else { } else {
characterId = await System.authPostToServer<string>(`character/add`, { characterId = await System.authPostToServer<string>(`character/add`, {
bookId: book?.bookId, bookId: book?.bookId,
character: updatedCharacter, character: updatedCharacter,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!characterId) { if (!characterId) {
errorMessage(t("characterComponent.errorAddCharacter")); errorMessage(t("characterComponent.errorAddCharacter"));
return; return;
@@ -150,11 +161,17 @@ export function CharacterComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:character:update', { response = await window.electron.invoke<boolean>('db:character:update', {
character: updatedCharacter, character: updatedCharacter,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:character:update', {
character: updatedCharacter,
});
} else { } else {
response = await System.authPostToServer<boolean>(`character/update`, { response = await System.authPostToServer<boolean>(`character/update`, {
character: updatedCharacter, character: updatedCharacter,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("characterComponent.errorUpdateCharacter")); errorMessage(t("characterComponent.errorUpdateCharacter"));
return; return;
@@ -204,6 +221,13 @@ export function CharacterComponent(props: any, ref: any) {
type: section, type: section,
name: value.name, name: value.name,
}); });
} else {
if (book?.localBook) {
attributeId = await window.electron.invoke<string>('db:character:attribute:add', {
characterId: selectedCharacter.id,
type: section,
name: value.name,
});
} else { } else {
attributeId = await System.authPostToServer<string>(`character/attribute/add`, { attributeId = await System.authPostToServer<string>(`character/attribute/add`, {
characterId: selectedCharacter.id, characterId: selectedCharacter.id,
@@ -211,6 +235,7 @@ export function CharacterComponent(props: any, ref: any) {
name: value.name, name: value.name,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!attributeId) { if (!attributeId) {
errorMessage(t("characterComponent.errorAddAttribute")); errorMessage(t("characterComponent.errorAddAttribute"));
return; return;
@@ -250,11 +275,17 @@ export function CharacterComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:character:attribute:delete', { response = await window.electron.invoke<boolean>('db:character:attribute:delete', {
attributeId: attrId, attributeId: attrId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:character:attribute:delete', {
attributeId: attrId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>(`character/attribute/delete`, { response = await System.authDeleteToServer<boolean>(`character/attribute/delete`, {
attributeId: attrId, attributeId: attrId,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("characterComponent.errorRemoveAttribute")); errorMessage(t("characterComponent.errorRemoveAttribute"));
return; return;

View File

@@ -30,6 +30,7 @@ import CharacterSectionElement from "@/components/book/settings/characters/Chara
import {useTranslations} from "next-intl"; import {useTranslations} from "next-intl";
import {LangContext} from "@/context/LangContext"; import {LangContext} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {BookContext} from "@/context/BookContext";
interface CharacterDetailProps { interface CharacterDetailProps {
selectedCharacter: CharacterProps | null; selectedCharacter: CharacterProps | null;
@@ -57,6 +58,7 @@ export default function CharacterDetail(
const t = useTranslations(); const t = useTranslations();
const {lang} = useContext(LangContext); const {lang} = useContext(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext); const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
const {errorMessage} = useContext(AlertContext); const {errorMessage} = useContext(AlertContext);
@@ -73,11 +75,17 @@ export default function CharacterDetail(
response = await window.electron.invoke<CharacterAttribute>('db:character:attributes', { response = await window.electron.invoke<CharacterAttribute>('db:character:attributes', {
characterId: selectedCharacter?.id, characterId: selectedCharacter?.id,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<CharacterAttribute>('db:character:attributes', {
characterId: selectedCharacter?.id,
});
} else { } else {
response = await System.authGetQueryToServer<CharacterAttribute>(`character/attribute`, session.accessToken, lang, { response = await System.authGetQueryToServer<CharacterAttribute>(`character/attribute`, session.accessToken, lang, {
characterId: selectedCharacter?.id, characterId: selectedCharacter?.id,
}); });
} }
}
if (response) { if (response) {
setSelectedCharacter({ setSelectedCharacter({
id: selectedCharacter?.id ?? '', id: selectedCharacter?.id ?? '',

View File

@@ -80,9 +80,13 @@ function GuideLineSetting(props: any, ref: any) {
let response: GuideLineAI; let response: GuideLineAI;
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<GuideLineAI>('db:book:guideline:ai:get', {id: bookId}); response = await window.electron.invoke<GuideLineAI>('db:book:guideline:ai:get', {id: bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<GuideLineAI>('db:book:guideline:ai:get', {id: bookId});
} else { } else {
response = await System.authGetQueryToServer<GuideLineAI>(`book/ai/guideline`, userToken, lang, {id: bookId}); response = await System.authGetQueryToServer<GuideLineAI>(`book/ai/guideline`, userToken, lang, {id: bookId});
} }
}
if (response) { if (response) {
setPlotSummary(response.globalResume); setPlotSummary(response.globalResume);
setVerbTense(response.verbeTense?.toString() || ''); setVerbTense(response.verbeTense?.toString() || '');
@@ -106,6 +110,9 @@ function GuideLineSetting(props: any, ref: any) {
let response: GuideLine; let response: GuideLine;
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<GuideLine>('db:book:guideline:get', {id: bookId}); response = await window.electron.invoke<GuideLine>('db:book:guideline:get', {id: bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<GuideLine>('db:book:guideline:get', {id: bookId});
} else { } else {
response = await System.authGetQueryToServer<GuideLine>( response = await System.authGetQueryToServer<GuideLine>(
`book/guide-line`, `book/guide-line`,
@@ -114,6 +121,7 @@ function GuideLineSetting(props: any, ref: any) {
{id: bookId}, {id: bookId},
); );
} }
}
if (response) { if (response) {
setTone(response.tone); setTone(response.tone);
setAtmosphere(response.atmosphere); setAtmosphere(response.atmosphere);
@@ -153,6 +161,9 @@ function GuideLineSetting(props: any, ref: any) {
}; };
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:guideline:update', guidelineData); response = await window.electron.invoke<boolean>('db:book:guideline:update', guidelineData);
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:guideline:update', guidelineData);
} else { } else {
response = await System.authPostToServer<boolean>( response = await System.authPostToServer<boolean>(
'book/guide-line', 'book/guide-line',
@@ -161,6 +172,7 @@ function GuideLineSetting(props: any, ref: any) {
lang, lang,
); );
} }
}
if (!response) { if (!response) {
errorMessage(t("guideLineSetting.saveError")); errorMessage(t("guideLineSetting.saveError"));
return; return;
@@ -177,7 +189,32 @@ function GuideLineSetting(props: any, ref: any) {
async function saveQuillSense(): Promise<void> { async function saveQuillSense(): Promise<void> {
try { try {
const response: boolean = await System.authPostToServer<boolean>( let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:guideline:ai:update', {
bookId: bookId,
plotSummary: plotSummary,
verbTense: verbTense,
narrativeType: narrativeType,
dialogueType: dialogueType,
toneAtmosphere: toneAtmosphere,
language: language,
themes: themes,
});
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:guideline:ai:update', {
bookId: bookId,
plotSummary: plotSummary,
verbTense: verbTense,
narrativeType: narrativeType,
dialogueType: dialogueType,
toneAtmosphere: toneAtmosphere,
language: language,
themes: themes,
});
} else {
response = await System.authPostToServer<boolean>(
'quillsense/book/guide-line', 'quillsense/book/guide-line',
{ {
bookId: bookId, bookId: bookId,
@@ -192,6 +229,8 @@ function GuideLineSetting(props: any, ref: any) {
userToken, userToken,
lang, lang,
); );
}
}
if (response) { if (response) {
successMessage(t("guideLineSetting.saveSuccess")); successMessage(t("guideLineSetting.saveSuccess"));
} else { } else {

View File

@@ -63,11 +63,15 @@ export function LocationComponent(props: any, ref: any) {
let response: LocationProps[]; let response: LocationProps[];
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<LocationProps[]>('db:location:all', {bookid: bookId}); response = await window.electron.invoke<LocationProps[]>('db:location:all', {bookid: bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<LocationProps[]>('db:location:all', {bookid: bookId});
} else { } else {
response = await System.authGetQueryToServer<LocationProps[]>(`location/all`, token, lang, { response = await System.authGetQueryToServer<LocationProps[]>(`location/all`, token, lang, {
bookid: bookId, bookid: bookId,
}); });
} }
}
if (response && response.length > 0) { if (response && response.length > 0) {
setSections(response); setSections(response);
} }
@@ -92,12 +96,19 @@ export function LocationComponent(props: any, ref: any) {
bookId: bookId, bookId: bookId,
locationName: newSectionName, locationName: newSectionName,
}); });
} else {
if (book?.localBook) {
sectionId = await window.electron.invoke<string>('db:location:section:add', {
bookId: bookId,
locationName: newSectionName,
});
} else { } else {
sectionId = await System.authPostToServer<string>(`location/section/add`, { sectionId = await System.authPostToServer<string>(`location/section/add`, {
bookId: bookId, bookId: bookId,
locationName: newSectionName, locationName: newSectionName,
}, token, lang); }, token, lang);
} }
}
if (!sectionId) { if (!sectionId) {
errorMessage(t('locationComponent.errorUnknownAddSection')); errorMessage(t('locationComponent.errorUnknownAddSection'));
return; return;
@@ -131,6 +142,13 @@ export function LocationComponent(props: any, ref: any) {
locationId: sectionId, locationId: sectionId,
elementName: newElementNames[sectionId], elementName: newElementNames[sectionId],
}); });
} else {
if (book?.localBook) {
elementId = await window.electron.invoke<string>('db:location:element:add', {
bookId: bookId,
locationId: sectionId,
elementName: newElementNames[sectionId],
});
} else { } else {
elementId = await System.authPostToServer<string>(`location/element/add`, { elementId = await System.authPostToServer<string>(`location/element/add`, {
bookId: bookId, bookId: bookId,
@@ -139,6 +157,7 @@ export function LocationComponent(props: any, ref: any) {
}, },
token, lang); token, lang);
} }
}
if (!elementId) { if (!elementId) {
errorMessage(t('locationComponent.errorUnknownAddElement')); errorMessage(t('locationComponent.errorUnknownAddElement'));
return; return;
@@ -197,12 +216,19 @@ export function LocationComponent(props: any, ref: any) {
elementId: sections[sectionIndex].elements[elementIndex].id, elementId: sections[sectionIndex].elements[elementIndex].id,
subElementName: newSubElementNames[elementIndex], subElementName: newSubElementNames[elementIndex],
}); });
} else {
if (book?.localBook) {
subElementId = await window.electron.invoke<string>('db:location:subelement:add', {
elementId: sections[sectionIndex].elements[elementIndex].id,
subElementName: newSubElementNames[elementIndex],
});
} else { } else {
subElementId = await System.authPostToServer<string>(`location/sub-element/add`, { subElementId = await System.authPostToServer<string>(`location/sub-element/add`, {
elementId: sections[sectionIndex].elements[elementIndex].id, elementId: sections[sectionIndex].elements[elementIndex].id,
subElementName: newSubElementNames[elementIndex], subElementName: newSubElementNames[elementIndex],
}, token, lang); }, token, lang);
} }
}
if (!subElementId) { if (!subElementId) {
errorMessage(t('locationComponent.errorUnknownAddSubElement')); errorMessage(t('locationComponent.errorUnknownAddSubElement'));
return; return;
@@ -253,11 +279,17 @@ export function LocationComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:location:element:delete', { response = await window.electron.invoke<boolean>('db:location:element:delete', {
elementId: elementId, elementId: elementId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:location:element:delete', {
elementId: elementId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>(`location/element/delete`, { response = await System.authDeleteToServer<boolean>(`location/element/delete`, {
elementId: elementId, elementId: elementId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('locationComponent.errorUnknownDeleteElement')); errorMessage(t('locationComponent.errorUnknownDeleteElement'));
return; return;
@@ -287,11 +319,17 @@ export function LocationComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:location:subelement:delete', { response = await window.electron.invoke<boolean>('db:location:subelement:delete', {
subElementId: subElementId, subElementId: subElementId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:location:subelement:delete', {
subElementId: subElementId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>(`location/sub-element/delete`, { response = await System.authDeleteToServer<boolean>(`location/sub-element/delete`, {
subElementId: subElementId, subElementId: subElementId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('locationComponent.errorUnknownDeleteSubElement')); errorMessage(t('locationComponent.errorUnknownDeleteSubElement'));
return; return;
@@ -316,11 +354,17 @@ export function LocationComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:location:delete', { response = await window.electron.invoke<boolean>('db:location:delete', {
locationId: sectionId, locationId: sectionId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:location:delete', {
locationId: sectionId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>(`location/delete`, { response = await System.authDeleteToServer<boolean>(`location/delete`, {
locationId: sectionId, locationId: sectionId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('locationComponent.errorUnknownDeleteSection')); errorMessage(t('locationComponent.errorUnknownDeleteSection'));
return; return;
@@ -343,11 +387,17 @@ export function LocationComponent(props: any, ref: any) {
response = await window.electron.invoke<boolean>('db:location:update', { response = await window.electron.invoke<boolean>('db:location:update', {
locations: sections, locations: sections,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:location:update', {
locations: sections,
});
} else { } else {
response = await System.authPostToServer<boolean>(`location/update`, { response = await System.authPostToServer<boolean>(`location/update`, {
locations: sections, locations: sections,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('locationComponent.errorUnknownSave')); errorMessage(t('locationComponent.errorUnknownSave'));
return; return;

View File

@@ -79,12 +79,19 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
bookId, bookId,
name: newIncidentTitle, name: newIncidentTitle,
}); });
} else {
if (book?.localBook) {
incidentId = await window.electron.invoke<string>('db:book:incident:add', {
bookId,
name: newIncidentTitle,
});
} else { } else {
incidentId = await System.authPostToServer<string>('book/incident/new', { incidentId = await System.authPostToServer<string>('book/incident/new', {
bookId, bookId,
name: newIncidentTitle, name: newIncidentTitle,
}, token, lang); }, token, lang);
} }
}
if (!incidentId) { if (!incidentId) {
errorMessage(t('errorAddIncident')); errorMessage(t('errorAddIncident'));
return; return;
@@ -124,12 +131,19 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
bookId, bookId,
incidentId, incidentId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:incident:remove', {
bookId,
incidentId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>('book/incident/remove', { response = await System.authDeleteToServer<boolean>('book/incident/remove', {
bookId, bookId,
incidentId, incidentId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('errorDeleteIncident')); errorMessage(t('errorDeleteIncident'));
return; return;
@@ -165,6 +179,13 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
name: newPlotPointTitle, name: newPlotPointTitle,
incidentId: selectedIncidentId, incidentId: selectedIncidentId,
}); });
} else {
if (book?.localBook) {
plotId = await window.electron.invoke<string>('db:book:plot:add', {
bookId,
name: newPlotPointTitle,
incidentId: selectedIncidentId,
});
} else { } else {
plotId = await System.authPostToServer<string>('book/plot/new', { plotId = await System.authPostToServer<string>('book/plot/new', {
bookId, bookId,
@@ -172,6 +193,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
incidentId: selectedIncidentId, incidentId: selectedIncidentId,
}, token, lang); }, token, lang);
} }
}
if (!plotId) { if (!plotId) {
errorMessage(t('errorAddPlotPoint')); errorMessage(t('errorAddPlotPoint'));
return; return;
@@ -211,11 +233,17 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
response = await window.electron.invoke<boolean>('db:book:plot:remove', { response = await window.electron.invoke<boolean>('db:book:plot:remove', {
plotId: plotPointId, plotId: plotPointId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:plot:remove', {
plotId: plotPointId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>('book/plot/remove', { response = await System.authDeleteToServer<boolean>('book/plot/remove', {
plotId: plotPointId, plotId: plotPointId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('errorDeletePlotPoint')); errorMessage(t('errorDeletePlotPoint'));
return; return;
@@ -263,9 +291,13 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
}; };
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
linkId = await window.electron.invoke<string>('db:chapter:information:add', linkData); linkId = await window.electron.invoke<string>('db:chapter:information:add', linkData);
} else {
if (book?.localBook) {
linkId = await window.electron.invoke<string>('db:chapter:information:add', linkData);
} else { } else {
linkId = await System.authPostToServer<string>('chapter/resume/add', linkData, token, lang); linkId = await System.authPostToServer<string>('chapter/resume/add', linkData, token, lang);
} }
}
if (!linkId) { if (!linkId) {
errorMessage(t('errorLinkChapter')); errorMessage(t('errorLinkChapter'));
return; return;
@@ -345,11 +377,17 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
response = await window.electron.invoke<boolean>('db:chapter:information:remove', { response = await window.electron.invoke<boolean>('db:chapter:information:remove', {
chapterInfoId, chapterInfoId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:chapter:information:remove', {
chapterInfoId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>('chapter/resume/remove', { response = await System.authDeleteToServer<boolean>('chapter/resume/remove', {
chapterInfoId, chapterInfoId,
}, token, lang); }, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('errorUnlinkChapter')); errorMessage(t('errorUnlinkChapter'));
return; return;

View File

@@ -41,12 +41,19 @@ export default function Issues({issues, setIssues}: IssuesProps) {
bookId, bookId,
name: newIssueName, name: newIssueName,
}); });
} else {
if (book?.localBook) {
issueId = await window.electron.invoke<string>('db:book:issue:add', {
bookId,
name: newIssueName,
});
} else { } else {
issueId = await System.authPostToServer<string>('book/issue/add', { issueId = await System.authPostToServer<string>('book/issue/add', {
bookId, bookId,
name: newIssueName, name: newIssueName,
}, token, lang); }, token, lang);
} }
}
if (!issueId) { if (!issueId) {
errorMessage(t("issues.errorAdd")); errorMessage(t("issues.errorAdd"));
return; return;
@@ -80,6 +87,12 @@ export default function Issues({issues, setIssues}: IssuesProps) {
bookId, bookId,
issueId, issueId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:issue:remove', {
bookId,
issueId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>( response = await System.authDeleteToServer<boolean>(
'book/issue/remove', 'book/issue/remove',
@@ -91,6 +104,7 @@ export default function Issues({issues, setIssues}: IssuesProps) {
lang lang
); );
} }
}
if (response) { if (response) {
const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,); const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,);
setIssues(updatedIssues); setIssues(updatedIssues);

View File

@@ -87,9 +87,13 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
}; };
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:chapter:remove', deleteData); response = await window.electron.invoke<boolean>('db:chapter:remove', deleteData);
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:chapter:remove', deleteData);
} else { } else {
response = await System.authDeleteToServer<boolean>('chapter/remove', deleteData, token, lang); response = await System.authDeleteToServer<boolean>('chapter/remove', deleteData, token, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("mainChapter.errorDelete")); errorMessage(t("mainChapter.errorDelete"));
} }
@@ -119,9 +123,13 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
}; };
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
responseId = await window.electron.invoke<string>('db:chapter:add', chapterData); responseId = await window.electron.invoke<string>('db:chapter:add', chapterData);
} else {
if (book?.localBook) {
responseId = await window.electron.invoke<string>('db:chapter:add', chapterData);
} else { } else {
responseId = await System.authPostToServer<string>('chapter/add', chapterData, token); responseId = await System.authPostToServer<string>('chapter/add', chapterData, token);
} }
}
if (!responseId) { if (!responseId) {
errorMessage(t("mainChapter.errorAdd")); errorMessage(t("mainChapter.errorAdd"));
return; return;

View File

@@ -73,11 +73,15 @@ export function Story(props: any, ref: any) {
let response: StoryFetchData; let response: StoryFetchData;
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<StoryFetchData>('db:book:story:get', {bookid: bookId}); response = await window.electron.invoke<StoryFetchData>('db:book:story:get', {bookid: bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<StoryFetchData>('db:book:story:get', {bookid: bookId});
} else { } else {
response = await System.authGetQueryToServer<StoryFetchData>(`book/story`, userToken, lang, { response = await System.authGetQueryToServer<StoryFetchData>(`book/story`, userToken, lang, {
bookid: bookId, bookid: bookId,
}); });
} }
}
if (response) { if (response) {
setActs(response.acts); setActs(response.acts);
setMainChapters(response.mainChapter); setMainChapters(response.mainChapter);
@@ -135,9 +139,13 @@ export function Story(props: any, ref: any) {
}; };
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:book:story:update', storyData); response = await window.electron.invoke<boolean>('db:book:story:update', storyData);
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:story:update', storyData);
} else { } else {
response = await System.authPostToServer<boolean>('book/story', storyData, userToken, lang); response = await System.authPostToServer<boolean>('book/story', storyData, userToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("story.errorSave")) errorMessage(t("story.errorSave"))
} }

View File

@@ -12,6 +12,7 @@ import InputField from "@/components/form/InputField";
import {useTranslations} from "next-intl"; import {useTranslations} from "next-intl";
import {LangContext, LangContextProps} from "@/context/LangContext"; import {LangContext, LangContextProps} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {BookContext} from "@/context/BookContext";
interface WorldElementInputProps { interface WorldElementInputProps {
sectionLabel: string; sectionLabel: string;
@@ -22,6 +23,7 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
const t = useTranslations(); const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext); const {lang} = useContext<LangContextProps>(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext); const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const {worlds, setWorlds, selectedWorldIndex} = useContext(WorldContext); const {worlds, setWorlds, selectedWorldIndex} = useContext(WorldContext);
const {errorMessage, successMessage} = useContext(AlertContext); const {errorMessage, successMessage} = useContext(AlertContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
@@ -39,11 +41,17 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
response = await window.electron.invoke<boolean>('db:book:world:element:remove', { response = await window.electron.invoke<boolean>('db:book:world:element:remove', {
elementId: elementId, elementId: elementId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:world:element:remove', {
elementId: elementId,
});
} else { } else {
response = await System.authDeleteToServer<boolean>('book/world/element/delete', { response = await System.authDeleteToServer<boolean>('book/world/element/delete', {
elementId: elementId, elementId: elementId,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("worldSetting.unknownError")) errorMessage(t("worldSetting.unknownError"))
} }
@@ -75,6 +83,13 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
worldId: worlds[selectedWorldIndex].id, worldId: worlds[selectedWorldIndex].id,
elementName: newElementName, elementName: newElementName,
}); });
} else {
if (book?.localBook) {
elementId = await window.electron.invoke<string>('db:book:world:element:add', {
elementType: section,
worldId: worlds[selectedWorldIndex].id,
elementName: newElementName,
});
} else { } else {
elementId = await System.authPostToServer('book/world/element/add', { elementId = await System.authPostToServer('book/world/element/add', {
elementType: section, elementType: section,
@@ -82,6 +97,7 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
elementName: newElementName, elementName: newElementName,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!elementId) { if (!elementId) {
errorMessage(t("worldSetting.unknownError")) errorMessage(t("worldSetting.unknownError"))
return; return;

View File

@@ -54,11 +54,15 @@ export function WorldSetting(props: any, ref: any) {
let response: WorldProps[]; let response: WorldProps[];
if (isCurrentlyOffline()) { if (isCurrentlyOffline()) {
response = await window.electron.invoke<WorldProps[]>('db:book:worlds:get', {bookid: bookId}); response = await window.electron.invoke<WorldProps[]>('db:book:worlds:get', {bookid: bookId});
} else {
if (book?.localBook) {
response = await window.electron.invoke<WorldProps[]>('db:book:worlds:get', {bookid: bookId});
} else { } else {
response = await System.authGetQueryToServer<WorldProps[]>(`book/worlds`, session.accessToken, lang, { response = await System.authGetQueryToServer<WorldProps[]>(`book/worlds`, session.accessToken, lang, {
bookid: bookId, bookid: bookId,
}); });
} }
}
if (response) { if (response) {
setWorlds(response); setWorlds(response);
const formattedWorlds: SelectBoxProps[] = response.map( const formattedWorlds: SelectBoxProps[] = response.map(
@@ -90,12 +94,19 @@ export function WorldSetting(props: any, ref: any) {
worldName: newWorldName, worldName: newWorldName,
bookId: bookId, bookId: bookId,
}); });
} else {
if (book?.localBook) {
worldId = await window.electron.invoke<string>('db:book:world:add', {
worldName: newWorldName,
bookId: bookId,
});
} else { } else {
worldId = await System.authPostToServer<string>('book/world/add', { worldId = await System.authPostToServer<string>('book/world/add', {
worldName: newWorldName, worldName: newWorldName,
bookId: bookId, bookId: bookId,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!worldId) { if (!worldId) {
errorMessage(t("worldSetting.addWorldError")); errorMessage(t("worldSetting.addWorldError"));
return; return;
@@ -146,12 +157,19 @@ export function WorldSetting(props: any, ref: any) {
world: worlds[selectedWorldIndex], world: worlds[selectedWorldIndex],
bookId: bookId, bookId: bookId,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:book:world:update', {
world: worlds[selectedWorldIndex],
bookId: bookId,
});
} else { } else {
response = await System.authPutToServer<boolean>('book/world/update', { response = await System.authPutToServer<boolean>('book/world/update', {
world: worlds[selectedWorldIndex], world: worlds[selectedWorldIndex],
bookId: bookId, bookId: bookId,
}, session.accessToken, lang); }, session.accessToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t("worldSetting.updateWorldError")); errorMessage(t("worldSetting.updateWorldError"));
return; return;

View File

@@ -32,6 +32,7 @@ import {LangContext, LangContextProps} from "@/context/LangContext";
import {BookTags} from "@/lib/models/Book"; import {BookTags} from "@/lib/models/Book";
import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext"; import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext";
import {configs} from "@/lib/configs"; import {configs} from "@/lib/configs";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
interface CompanionContent { interface CompanionContent {
version: number; version: number;
@@ -43,6 +44,7 @@ export default function DraftCompanion() {
const t = useTranslations(); const t = useTranslations();
const {setTotalPrice, setTotalCredits} = useContext<AIUsageContextProps>(AIUsageContext) const {setTotalPrice, setTotalCredits} = useContext<AIUsageContextProps>(AIUsageContext)
const {lang} = useContext<LangContextProps>(LangContext) const {lang} = useContext<LangContextProps>(LangContext)
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const mainEditor: Editor | null = useEditor({ const mainEditor: Editor | null = useEditor({
extensions: [ extensions: [
@@ -95,7 +97,7 @@ export default function DraftCompanion() {
const isGPTEnabled: boolean = QuillSense.isOpenAIEnabled(session); const isGPTEnabled: boolean = QuillSense.isOpenAIEnabled(session);
const isSubTierTree: boolean = QuillSense.getSubLevel(session) === 3; const isSubTierTree: boolean = QuillSense.getSubLevel(session) === 3;
const hasAccess: boolean = isGPTEnabled || isSubTierTree; const hasAccess: boolean = (isGPTEnabled || isSubTierTree) && !isCurrentlyOffline() && !book?.localBook;
useEffect((): void => { useEffect((): void => {
getDraftContent().then(); getDraftContent().then();
@@ -106,11 +108,28 @@ export default function DraftCompanion() {
async function getDraftContent(): Promise<void> { async function getDraftContent(): Promise<void> {
try { try {
const response: CompanionContent = await System.authGetQueryToServer<CompanionContent>(`chapter/content/companion`, session.accessToken, lang, { let response: CompanionContent | null;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<CompanionContent>('db:chapter:content:companion', {
bookid: book?.bookId, bookid: book?.bookId,
chapterid: chapter?.chapterId, chapterid: chapter?.chapterId,
version: chapter?.chapterContent.version, version: chapter?.chapterContent.version,
}); });
} else {
if (book?.localBook) {
response = await window.electron.invoke<CompanionContent>('db:chapter:content:companion', {
bookid: book?.bookId,
chapterid: chapter?.chapterId,
version: chapter?.chapterContent.version,
});
} else {
response = await System.authGetQueryToServer<CompanionContent>(`chapter/content/companion`, session.accessToken, lang, {
bookid: book?.bookId,
chapterid: chapter?.chapterId,
version: chapter?.chapterContent.version,
});
}
}
if (response && mainEditor) { if (response && mainEditor) {
mainEditor.commands.setContent(JSON.parse(response.content)); mainEditor.commands.setContent(JSON.parse(response.content));
setDraftVersion(response.version); setDraftVersion(response.version);
@@ -145,9 +164,18 @@ export default function DraftCompanion() {
async function fetchTags(): Promise<void> { async function fetchTags(): Promise<void> {
try { try {
const responseTags: BookTags = await System.authGetQueryToServer<BookTags>(`book/tags`, session.accessToken, lang, { let responseTags: BookTags | null;
if (isCurrentlyOffline()) {
responseTags = await window.electron.invoke<BookTags>('db:book:tags', book?.bookId);
} else {
if (book?.localBook) {
responseTags = await window.electron.invoke<BookTags>('db:book:tags', book?.bookId);
} else {
responseTags = await System.authGetQueryToServer<BookTags>(`book/tags`, session.accessToken, lang, {
bookId: book?.bookId bookId: book?.bookId
}); });
}
}
if (responseTags) { if (responseTags) {
setCharacters(responseTags.characters); setCharacters(responseTags.characters);
setLocations(responseTags.locations); setLocations(responseTags.locations);

View File

@@ -22,6 +22,7 @@ import {ChapterContext} from '@/context/ChapterContext';
import System from '@/lib/models/System'; import System from '@/lib/models/System';
import {AlertContext} from '@/context/AlertContext'; import {AlertContext} from '@/context/AlertContext';
import {SessionContext} from "@/context/SessionContext"; import {SessionContext} from "@/context/SessionContext";
import {BookContext} from '@/context/BookContext';
import DraftCompanion from "@/components/editor/DraftCompanion"; import DraftCompanion from "@/components/editor/DraftCompanion";
import GhostWriter from "@/components/ghostwriter/GhostWriter"; import GhostWriter from "@/components/ghostwriter/GhostWriter";
import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading"; import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading";
@@ -135,6 +136,7 @@ export default function TextEditor() {
const {lang} = useContext<LangContextProps>(LangContext) const {lang} = useContext<LangContextProps>(LangContext)
const {editor} = useContext(EditorContext); const {editor} = useContext(EditorContext);
const {chapter} = useContext(ChapterContext); const {chapter} = useContext(ChapterContext);
const {book} = useContext(BookContext);
const {errorMessage, successMessage} = useContext(AlertContext); const {errorMessage, successMessage} = useContext(AlertContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext); const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
@@ -293,6 +295,15 @@ export default function TextEditor() {
totalWordCount: editor.getText().length, totalWordCount: editor.getText().length,
currentTime: mainTimer currentTime: mainTimer
}) })
} else {
if (book?.localBook) {
response = await window.electron.invoke<boolean>('db:chapter:content:save',{
chapterId,
version,
content,
totalWordCount: editor.getText().length,
currentTime: mainTimer
})
} else { } else {
response = await System.authPostToServer<boolean>(`chapter/content`, { response = await System.authPostToServer<boolean>(`chapter/content`, {
chapterId, chapterId,
@@ -302,6 +313,7 @@ export default function TextEditor() {
currentTime: mainTimer currentTime: mainTimer
}, session?.accessToken, lang); }, session?.accessToken, lang);
} }
}
if (!response) { if (!response) {
errorMessage(t('editor.error.savedFailed')); errorMessage(t('editor.error.savedFailed'));
setIsSaving(false); setIsSaving(false);
@@ -467,7 +479,7 @@ export default function TextEditor() {
onClick={handleShowUserSettings} onClick={handleShowUserSettings}
icon={faCog} icon={faCog}
/> />
{chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && ( {chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && !book?.localBook && (
<CollapsableButton <CollapsableButton
showCollapsable={showGhostWriter} showCollapsable={showGhostWriter}
text={t("textEditor.ghostWriter")} text={t("textEditor.ghostWriter")}

View File

@@ -171,7 +171,7 @@ export default function ComposerRightBar() {
<div className="bg-tertiary border-l border-secondary/50 p-3 flex flex-col space-y-3 shadow-xl"> <div className="bg-tertiary border-l border-secondary/50 p-3 flex flex-col space-y-3 shadow-xl">
{book ? editorComponents {book ? editorComponents
.filter((component: PanelComponent):boolean => { .filter((component: PanelComponent):boolean => {
return !(isCurrentlyOffline() && component.id === 1); return !((isCurrentlyOffline() || book?.localBook) && component.id === 1);
}) })
.map((component: PanelComponent) => ( .map((component: PanelComponent) => (
<button <button