- Refactor components to support conditional offline and online CRUD operations. - Introduce `addToQueue` mechanism for syncing offline changes to the server. - Add `isChapterContentExist` method and related existence checks in repositories. - Consolidate data structures and streamline book, chapter, character, and guideline synchronization workflows. - Encrypt additional character fields and adjust repository inserts for offline data.
173 lines
8.0 KiB
TypeScript
173 lines
8.0 KiB
TypeScript
'use client';
|
|
|
|
import {ChangeEvent, useContext, useState} from "react";
|
|
import {WorldContext} from "@/context/WorldContext";
|
|
import TextInput from "@/components/form/TextInput";
|
|
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
|
import {WorldElement, WorldProps} from "@/lib/models/World";
|
|
import {AlertContext} from "@/context/AlertContext";
|
|
import {SessionContext} from "@/context/SessionContext";
|
|
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";
|
|
import {BookContext} from "@/context/BookContext";
|
|
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from "@/context/SyncQueueContext";
|
|
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
|
|
import {SyncedBook} from "@/lib/models/SyncedBook";
|
|
|
|
interface WorldElementInputProps {
|
|
sectionLabel: string;
|
|
sectionType: string;
|
|
}
|
|
|
|
export default function WorldElementComponent({sectionLabel, sectionType}: WorldElementInputProps) {
|
|
const t = useTranslations();
|
|
const {lang} = useContext<LangContextProps>(LangContext);
|
|
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
|
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
|
const {localSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
|
const {book} = useContext(BookContext);
|
|
const {worlds, setWorlds, selectedWorldIndex} = useContext(WorldContext);
|
|
const {errorMessage, successMessage} = useContext(AlertContext);
|
|
const {session} = useContext(SessionContext);
|
|
|
|
const [newElementName, setNewElementName] = useState<string>('');
|
|
|
|
async function handleRemoveElement(
|
|
section: keyof WorldProps,
|
|
index: number,
|
|
): Promise<void> {
|
|
try {
|
|
let response: boolean;
|
|
const elementId = (worlds[selectedWorldIndex][section] as WorldElement[])[index].id;
|
|
if (isCurrentlyOffline() || book?.localBook) {
|
|
response = await window.electron.invoke<boolean>('db:book:world:element:remove', {
|
|
elementId: elementId,
|
|
});
|
|
} else {
|
|
response = await System.authDeleteToServer<boolean>('book/world/element/delete', {
|
|
elementId: elementId,
|
|
}, session.accessToken, lang);
|
|
|
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
|
addToQueue('db:book:world:element:remove', {
|
|
elementId: elementId,
|
|
});
|
|
}
|
|
}
|
|
if (!response) {
|
|
errorMessage(t("worldSetting.unknownError"))
|
|
}
|
|
const updatedWorlds: WorldProps[] = [...worlds];
|
|
(updatedWorlds[selectedWorldIndex][section] as WorldElement[]).splice(
|
|
index,
|
|
1,
|
|
);
|
|
setWorlds(updatedWorlds);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.toString());
|
|
} else {
|
|
errorMessage(t("worldElementComponent.errorUnknown"));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function handleAddElement(section: keyof WorldProps): Promise<void> {
|
|
if (newElementName.trim() === '') {
|
|
errorMessage(t("worldElementComponent.emptyField", {section: sectionLabel}));
|
|
return;
|
|
}
|
|
try {
|
|
let elementId: string;
|
|
if (isCurrentlyOffline() || book?.localBook) {
|
|
elementId = await window.electron.invoke<string>('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 (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
|
addToQueue('db:book:world:element:add', {
|
|
elementType: section,
|
|
worldId: worlds[selectedWorldIndex].id,
|
|
elementId,
|
|
elementName: newElementName,
|
|
});
|
|
}
|
|
}
|
|
if (!elementId) {
|
|
errorMessage(t("worldSetting.unknownError"))
|
|
return;
|
|
}
|
|
const updatedWorlds: WorldProps[] = [...worlds];
|
|
(updatedWorlds[selectedWorldIndex][section] as WorldElement[]).push({
|
|
id: elementId,
|
|
name: newElementName,
|
|
description: '',
|
|
});
|
|
setWorlds(updatedWorlds);
|
|
setNewElementName('');
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t("worldElementComponent.errorUnknown"));
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleElementChange(
|
|
section: keyof WorldProps,
|
|
index: number,
|
|
field: keyof WorldElement,
|
|
value: string,
|
|
): void {
|
|
const updatedWorlds: WorldProps[] = [...worlds];
|
|
const sectionElements = updatedWorlds[selectedWorldIndex][
|
|
section
|
|
] as WorldElement[];
|
|
sectionElements[index] = {...sectionElements[index], [field]: value};
|
|
setWorlds(updatedWorlds);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{Array.isArray(worlds[selectedWorldIndex][sectionType as keyof WorldProps]) &&
|
|
(worlds[selectedWorldIndex][sectionType as keyof WorldProps] as WorldElement[]).map(
|
|
(element: WorldElement, index: number) => (
|
|
<div key={element.id}
|
|
className="bg-secondary/30 rounded-xl p-4 border-l-4 border-primary shadow-sm hover:shadow-md transition-all duration-200">
|
|
<div className="mb-2">
|
|
<InputField input={<TextInput
|
|
value={element.name}
|
|
setValue={(e: ChangeEvent<HTMLInputElement>) => handleElementChange(sectionType as keyof WorldProps, index, 'name', e.target.value)}
|
|
placeholder={t("worldElementComponent.namePlaceholder", {section: sectionLabel.toLowerCase()})}
|
|
/>}
|
|
removeButtonCallBack={(): Promise<void> => handleRemoveElement(sectionType as keyof WorldProps, index)}/>
|
|
</div>
|
|
<TexteAreaInput
|
|
value={element.description}
|
|
setValue={(e) => handleElementChange(sectionType as keyof WorldProps, index, 'description', e.target.value)}
|
|
placeholder={t("worldElementComponent.descriptionPlaceholder", {section: sectionLabel.toLowerCase()})}
|
|
/>
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
<InputField input={<TextInput
|
|
value={newElementName}
|
|
setValue={(e: ChangeEvent<HTMLInputElement>): void => setNewElementName(e.target.value)}
|
|
placeholder={t("worldElementComponent.newPlaceholder", {section: sectionLabel.toLowerCase()})}
|
|
/>} addButtonCallBack={(): Promise<void> => handleAddElement(sectionType as keyof WorldProps)}/>
|
|
</div>
|
|
);
|
|
} |