Add offline mode logic for chapter operations and refactor IPC handlers

- Integrate `OfflineContext` into `TextEditor` and `ScribeChapterComponent` to handle offline scenarios for chapter CRUD operations.
- Add conditional logic to switch between server API requests and offline IPC handlers (`db:chapter:add`, `db:chapter:remove`, `db:chapter:content:save`, `db:chapter:update`).
- Refactor and rename IPC handlers (`db:chapter:create` to `db:chapter:add`, `db:chapter:delete` to `db:chapter:remove`) for consistency.
- Update UI to disable certain actions when offline (e.g., GhostWriter button).
This commit is contained in:
natreex
2025-11-26 22:03:22 -05:00
parent 9648d9e9be
commit e1abcd9490
3 changed files with 48 additions and 22 deletions

View File

@@ -30,6 +30,7 @@ import {IconDefinition} from "@fortawesome/fontawesome-svg-core";
import UserEditorSettings, {EditorDisplaySettings} from "@/components/editor/UserEditorSetting"; import UserEditorSettings, {EditorDisplaySettings} from "@/components/editor/UserEditorSetting";
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";
interface ToolbarButton { interface ToolbarButton {
action: () => void; action: () => void;
@@ -136,6 +137,7 @@ export default function TextEditor() {
const {chapter} = useContext(ChapterContext); const {chapter} = useContext(ChapterContext);
const {errorMessage, successMessage} = useContext(AlertContext); const {errorMessage, successMessage} = useContext(AlertContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const [mainTimer, setMainTimer] = useState<number>(0); const [mainTimer, setMainTimer] = useState<number>(0);
const [showDraftCompanion, setShowDraftCompanion] = useState<boolean>(false); const [showDraftCompanion, setShowDraftCompanion] = useState<boolean>(false);
@@ -282,13 +284,24 @@ export default function TextEditor() {
const version: number = chapter.chapterContent.version || 0; const version: number = chapter.chapterContent.version || 0;
try { try {
const response: boolean = await System.authPostToServer<boolean>(`chapter/content`, { let response: boolean;
if (isCurrentlyOffline()){
response = await window.electron.invoke<boolean>('db:chapter:content:save',{
chapterId,
version,
content,
totalWordCount: editor.getText().length,
currentTime: mainTimer
})
} else {
response = await System.authPostToServer<boolean>(`chapter/content`, {
chapterId, chapterId,
version, version,
content, content,
totalWordCount: editor.getText().length, totalWordCount: editor.getText().length,
currentTime: mainTimer currentTime: mainTimer
}, session?.accessToken ?? ''); }, session?.accessToken ?? '');
}
if (!response) { if (!response) {
errorMessage(t('editor.error.savedFailed')); errorMessage(t('editor.error.savedFailed'));
setIsSaving(false); setIsSaving(false);
@@ -437,8 +450,7 @@ export default function TextEditor() {
onClick={button.action} onClick={button.action}
className={`group flex items-center px-3 py-2 rounded-lg transition-all duration-200 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`} className={`group flex items-center px-3 py-2 rounded-lg transition-all duration-200 ${button.isActive ? 'bg-primary text-text-primary shadow-md shadow-primary/30 scale-105' : 'text-muted hover:text-text-primary hover:bg-secondary/50 hover:shadow-sm hover:scale-105'}`}
> >
<FontAwesomeIcon icon={button.icon} <FontAwesomeIcon icon={button.icon} className={'w-4 h-4 transition-transform duration-200 group-hover:scale-110'}/>
className={'w-4 h-4 transition-transform duration-200 group-hover:scale-110'}/>
{ {
button.label && button.label &&
<span className="ml-2 text-sm font-medium"> <span className="ml-2 text-sm font-medium">
@@ -455,7 +467,7 @@ export default function TextEditor() {
onClick={handleShowUserSettings} onClick={handleShowUserSettings}
icon={faCog} icon={faCog}
/> />
{chapter?.chapterContent.version === 2 && ( {chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && (
<CollapsableButton <CollapsableButton
showCollapsable={showGhostWriter} showCollapsable={showGhostWriter}
text={t("textEditor.ghostWriter")} text={t("textEditor.ghostWriter")}

View File

@@ -123,11 +123,20 @@ export default function ScribeChapterComponent() {
async function handleChapterUpdate(chapterId: string, title: string, chapterOrder: number): Promise<void> { async function handleChapterUpdate(chapterId: string, title: string, chapterOrder: number): Promise<void> {
try { try {
const response: boolean = await System.authPostToServer<boolean>('chapter/update', { let response: boolean;
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:chapter:update',{
chapterId: chapterId,
chapterOrder: chapterOrder,
title: title,
})
} else {
response = await System.authPostToServer<boolean>('chapter/update', {
chapterId: chapterId, chapterId: chapterId,
chapterOrder: chapterOrder, chapterOrder: chapterOrder,
title: title, title: title,
}, userToken, lang); }, userToken, lang);
}
if (!response) { if (!response) {
errorMessage(t("scribeChapterComponent.errorChapterUpdate")); errorMessage(t("scribeChapterComponent.errorChapterUpdate"));
return; return;
@@ -159,10 +168,14 @@ export default function ScribeChapterComponent() {
async function handleDeleteChapter(): Promise<void> { async function handleDeleteChapter(): Promise<void> {
try { try {
setDeleteConfirmationMessage(false); setDeleteConfirmationMessage(false);
const response: boolean = await System.authDeleteToServer<boolean>('chapter/remove', { let response:boolean = false;
bookId: book?.bookId, if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:chapter:remove', removeChapterId)
} else {
response = await System.authDeleteToServer<boolean>('chapter/remove', {
chapterId: removeChapterId, chapterId: removeChapterId,
}, userToken, lang); }, userToken, lang);
}
if (!response) { if (!response) {
errorMessage(t("scribeChapterComponent.errorChapterDelete")); errorMessage(t("scribeChapterComponent.errorChapterDelete"));
return; return;
@@ -189,7 +202,7 @@ export default function ScribeChapterComponent() {
try { try {
let chapterId:string|null = null; let chapterId:string|null = null;
if (isCurrentlyOffline()){ if (isCurrentlyOffline()){
chapterId = await window.electron.invoke<string>('db:chapter:create', { chapterId = await window.electron.invoke<string>('db:chapter:add', {
bookId: book?.bookId, bookId: book?.bookId,
chapterOrder: chapterOrder, chapterOrder: chapterOrder,
title: chapterTitle title: chapterTitle

View File

@@ -107,7 +107,7 @@ ipcMain.handle('db:chapter:last', createHandler<string, ChapterProps | null>(
); );
// POST /chapter/add - Add new chapter // POST /chapter/add - Add new chapter
ipcMain.handle('db:chapter:create', createHandler<AddChapterData, string>( ipcMain.handle('db:chapter:add', createHandler<AddChapterData, string>(
function(userId: string, data: AddChapterData, lang: 'fr' | 'en'): string { function(userId: string, data: AddChapterData, lang: 'fr' | 'en'): string {
return Chapter.addChapter(userId, data.bookId, data.title, 0, data.chapterOrder, lang); return Chapter.addChapter(userId, data.bookId, data.title, 0, data.chapterOrder, lang);
} }
@@ -115,8 +115,9 @@ ipcMain.handle('db:chapter:create', createHandler<AddChapterData, string>(
); );
// DELETE /chapter/remove - Remove chapter // DELETE /chapter/remove - Remove chapter
ipcMain.handle('db:chapter:delete', createHandler<string, boolean>( ipcMain.handle('db:chapter:remove', createHandler<string, boolean>(
function(userId: string, chapterId: string, lang: 'fr' | 'en'): boolean { function(userId: string, chapterId: string, lang: 'fr' | 'en'): boolean {
console.log(userId,chapterId,lang)
return Chapter.removeChapter(userId, chapterId, lang); return Chapter.removeChapter(userId, chapterId, lang);
} }
) )