Add offline mode logic for main chapters and refine IPC handlers

- Integrate `OfflineContext` into `MainChapter` and related components to handle offline scenarios for chapter operations.
- Add conditional logic to toggle between server API requests and offline IPC handlers (`db:chapter:add`, `db:chapter:remove`).
- Extend `GET /book/story` endpoint to include `mainChapter` data.
- Refactor IPC handlers for chapters to accept structured data arguments with stricter typings.
- Update frontend key assignments for `BookList` component for improved rendering stability.
This commit is contained in:
natreex
2025-11-26 23:17:25 -05:00
parent 23f1592c22
commit db2c88a42d
4 changed files with 42 additions and 30 deletions

View File

@@ -253,8 +253,8 @@ export default function BookList() {
<div <div
className="flex justify-between items-center w-full max-w-5xl mx-auto mb-6 px-6"> className="flex justify-between items-center w-full max-w-5xl mx-auto mb-6 px-6">
<h2 className="text-3xl text-text-primary capitalize font-['ADLaM_Display'] flex items-center gap-3"> <h2 className="text-3xl text-text-primary capitalize font-['ADLaM_Display'] flex items-center gap-3">
<span key="icon" className="w-1 h-8 bg-primary rounded-full"></span> <span className="w-1 h-8 bg-primary rounded-full"></span>
<span key="title">{category}</span> <span>{category}</span>
</h2> </h2>
<span <span
className="text-muted text-lg font-medium bg-secondary/30 px-4 py-1.5 rounded-full">{books.length} {t("bookList.works")}</span> className="text-muted text-lg font-medium bg-secondary/30 px-4 py-1.5 rounded-full">{books.length} {t("bookList.works")}</span>
@@ -263,7 +263,7 @@ export default function BookList() {
<div className="flex flex-wrap justify-center items-start w-full px-4"> <div className="flex flex-wrap justify-center items-start w-full px-4">
{ {
books.map((book: BookProps, idx) => ( books.map((book: BookProps, idx) => (
<div key={book.bookId} <div key={book.bookId || `book-${idx}`}
{...(idx === 0 && {'data-guide': 'book-card'})} {...(idx === 0 && {'data-guide': 'book-card'})}
className={`w-full sm:w-1/3 md:w-1/4 lg:w-1/5 xl:w-1/6 p-2 box-border ${User.guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}> className={`w-full sm:w-1/3 md:w-1/4 lg:w-1/5 xl:w-1/6 p-2 box-border ${User.guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}>
<BookCard book={book} <BookCard book={book}

View File

@@ -12,6 +12,7 @@ import AlertBox from "@/components/AlertBox";
import CollapsableArea from "@/components/CollapsableArea"; import CollapsableArea from "@/components/CollapsableArea";
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";
interface MainChapterProps { interface MainChapterProps {
chapters: ChapterListProps[]; chapters: ChapterListProps[];
@@ -21,6 +22,7 @@ interface MainChapterProps {
export default function MainChapter({chapters, setChapters}: MainChapterProps) { export default function MainChapter({chapters, setChapters}: MainChapterProps) {
const t = useTranslations(); const t = useTranslations();
const {lang} = useContext(LangContext) const {lang} = useContext(LangContext)
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext); const {book} = useContext(BookContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
const {errorMessage, successMessage} = useContext(AlertContext); const {errorMessage, successMessage} = useContext(AlertContext);
@@ -78,15 +80,16 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
async function deleteChapter(): Promise<void> { async function deleteChapter(): Promise<void> {
try { try {
setDeleteConfirmMessage(false); setDeleteConfirmMessage(false);
const response: boolean = await System.authDeleteToServer<boolean>( let response: boolean;
'chapter/remove', const deleteData = {
{
bookId, bookId,
chapterId: chapterIdToRemove, chapterId: chapterIdToRemove,
}, };
token, if (isCurrentlyOffline()) {
lang, response = await window.electron.invoke<boolean>('db:chapter:remove', deleteData);
); } else {
response = await System.authDeleteToServer<boolean>('chapter/remove', deleteData, token, lang);
}
if (!response) { if (!response) {
errorMessage(t("mainChapter.errorDelete")); errorMessage(t("mainChapter.errorDelete"));
} }
@@ -107,16 +110,18 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
} }
try { try {
const responseId: string = await System.authPostToServer<string>( let responseId: string;
'chapter/add', const chapterData = {
{
bookId: bookId, bookId: bookId,
wordsCount: 0, wordsCount: 0,
chapterOrder: newChapterOrder ? newChapterOrder : 0, chapterOrder: newChapterOrder ? newChapterOrder : 0,
title: newChapterTitle, title: newChapterTitle,
}, };
token, if (isCurrentlyOffline()) {
); responseId = await window.electron.invoke<string>('db:chapter:add', chapterData);
} else {
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

@@ -2,6 +2,7 @@ import { ipcMain } from 'electron';
import { createHandler } from '../database/LocalSystem.js'; import { createHandler } from '../database/LocalSystem.js';
import Book from '../database/models/Book.js'; import Book from '../database/models/Book.js';
import type { BookProps, GuideLine, GuideLineAI, Act, Issue, WorldProps } from '../database/models/Book.js'; import type { BookProps, GuideLine, GuideLineAI, Act, Issue, WorldProps } from '../database/models/Book.js';
import Chapter from '../database/models/Chapter.js';
import type { ChapterProps } from '../database/models/Chapter.js'; import type { ChapterProps } from '../database/models/Chapter.js';
interface UpdateBookBasicData { interface UpdateBookBasicData {
@@ -30,6 +31,7 @@ interface UpdateGuideLineData {
interface StoryData { interface StoryData {
acts: Act[]; acts: Act[];
issues: Issue[]; issues: Issue[];
mainChapter: ChapterProps[];
} }
interface UpdateStoryData { interface UpdateStoryData {
@@ -144,7 +146,7 @@ ipcMain.handle('db:book:guideline:update', createHandler<UpdateGuideLineData, bo
) )
); );
// GET /book/story - Get story data (acts + issues) // GET /book/story - Get story data (acts + issues + mainChapter)
interface GetStoryData { interface GetStoryData {
bookid: string; bookid: string;
} }
@@ -152,9 +154,11 @@ ipcMain.handle('db:book:story:get', createHandler<GetStoryData, StoryData>(
async function(userId: string, data: GetStoryData, lang: 'fr' | 'en'):Promise<StoryData> { async function(userId: string, data: GetStoryData, lang: 'fr' | 'en'):Promise<StoryData> {
const acts:Act[] = await Book.getActsData(userId, data.bookid, lang); const acts:Act[] = await Book.getActsData(userId, data.bookid, lang);
const issues:Issue[] = await Book.getIssuesFromBook(userId, data.bookid, lang); const issues:Issue[] = await Book.getIssuesFromBook(userId, data.bookid, lang);
const mainChapter:ChapterProps[] = Chapter.getAllChaptersFromABook(userId, data.bookid, lang);
return { return {
acts, acts,
issues issues,
mainChapter
}; };
} }
) )

View File

@@ -115,10 +115,13 @@ ipcMain.handle('db:chapter:add', createHandler<AddChapterData, string>(
); );
// DELETE /chapter/remove - Remove chapter // DELETE /chapter/remove - Remove chapter
ipcMain.handle('db:chapter:remove', createHandler<string, boolean>( interface RemoveChapterData {
function(userId: string, chapterId: string, lang: 'fr' | 'en'): boolean { chapterId: string;
console.log(userId,chapterId,lang) bookId?: string;
return Chapter.removeChapter(userId, chapterId, lang); }
ipcMain.handle('db:chapter:remove', createHandler<RemoveChapterData, boolean>(
function(userId: string, data: RemoveChapterData, lang: 'fr' | 'en'): boolean {
return Chapter.removeChapter(userId, data.chapterId, lang);
} }
) )
); );