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:
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
};
|
||||||
},
|
if (isCurrentlyOffline()) {
|
||||||
token,
|
response = await window.electron.invoke<boolean>('db:chapter:remove', deleteData);
|
||||||
lang,
|
} else {
|
||||||
);
|
response = await System.authDeleteToServer<boolean>('chapter/remove', deleteData, token, lang);
|
||||||
|
}
|
||||||
if (!response) {
|
if (!response) {
|
||||||
errorMessage(t("mainChapter.errorDelete"));
|
errorMessage(t("mainChapter.errorDelete"));
|
||||||
}
|
}
|
||||||
@@ -105,18 +108,20 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
|
|||||||
if (newChapterTitle.trim() === '') {
|
if (newChapterTitle.trim() === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
};
|
||||||
},
|
if (isCurrentlyOffline()) {
|
||||||
token,
|
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;
|
||||||
@@ -130,7 +135,7 @@ export default function MainChapter({chapters, setChapters}: MainChapterProps) {
|
|||||||
};
|
};
|
||||||
setChapters([...chapters, newChapter]);
|
setChapters([...chapters, newChapter]);
|
||||||
setNewChapterTitle('');
|
setNewChapterTitle('');
|
||||||
|
|
||||||
setNewChapterOrder(newChapterOrder + 1);
|
setNewChapterOrder(newChapterOrder + 1);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user