- Removed unnecessary React imports. - Adjusted package.json scripts for Electron integration. - Updated components to replace Next.js-specific imports with Electron-compatible alternatives. - Minor tsconfig.json changes for better compatibility.
608 lines
24 KiB
TypeScript
608 lines
24 KiB
TypeScript
import {Dispatch, SetStateAction, useContext, useState} from 'react';
|
|
import {
|
|
faFire,
|
|
faFlag,
|
|
faPuzzlePiece,
|
|
faScaleBalanced,
|
|
faTrophy,
|
|
IconDefinition,
|
|
} from '@fortawesome/free-solid-svg-icons';
|
|
import {Act as ActType, Incident, PlotPoint} from '@/lib/models/Book';
|
|
import {ActChapter, ChapterListProps} from '@/lib/models/Chapter';
|
|
import System from '@/lib/models/System';
|
|
import {BookContext} from '@/context/BookContext';
|
|
import {SessionContext} from '@/context/SessionContext';
|
|
import {AlertContext} from '@/context/AlertContext';
|
|
import CollapsableArea from '@/components/CollapsableArea';
|
|
import ActDescription from '@/components/book/settings/story/act/ActDescription';
|
|
import ActChaptersSection from '@/components/book/settings/story/act/ActChaptersSection';
|
|
import ActIncidents from '@/components/book/settings/story/act/ActIncidents';
|
|
import ActPlotPoints from '@/components/book/settings/story/act/ActPlotPoints';
|
|
import {useTranslations} from 'next-intl';
|
|
import {LangContext, LangContextProps} from "@/context/LangContext";
|
|
|
|
interface ActProps {
|
|
acts: ActType[];
|
|
setActs: Dispatch<SetStateAction<ActType[]>>;
|
|
mainChapters: ChapterListProps[];
|
|
}
|
|
|
|
export default function Act({acts, setActs, mainChapters}: ActProps) {
|
|
const t = useTranslations('actComponent');
|
|
const {lang} = useContext<LangContextProps>(LangContext);
|
|
const {book} = useContext(BookContext);
|
|
const {session} = useContext(SessionContext);
|
|
const {errorMessage, successMessage} = useContext(AlertContext);
|
|
|
|
const bookId: string | undefined = book?.bookId;
|
|
const token: string = session.accessToken;
|
|
|
|
const [expandedSections, setExpandedSections] = useState<{
|
|
[key: string]: boolean;
|
|
}>({});
|
|
|
|
const [newIncidentTitle, setNewIncidentTitle] = useState<string>('');
|
|
const [newPlotPointTitle, setNewPlotPointTitle] = useState<string>('');
|
|
const [selectedIncidentId, setSelectedIncidentId] = useState<string>('');
|
|
|
|
function toggleSection(sectionKey: string): void {
|
|
setExpandedSections(prev => ({
|
|
...prev,
|
|
[sectionKey]: !prev[sectionKey],
|
|
}));
|
|
}
|
|
|
|
function updateActSummary(actId: number, summary: string): void {
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
return {...act, summary};
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
}
|
|
|
|
function getIncidents(): Incident[] {
|
|
const act2: ActType | undefined = acts.find((act: ActType): boolean => act.id === 2);
|
|
return act2?.incidents || [];
|
|
}
|
|
|
|
async function addIncident(actId: number): Promise<void> {
|
|
if (newIncidentTitle.trim() === '') return;
|
|
|
|
try {
|
|
const incidentId: string =
|
|
await System.authPostToServer<string>('book/incident/new', {
|
|
bookId,
|
|
name: newIncidentTitle,
|
|
}, token, lang);
|
|
if (!incidentId) {
|
|
errorMessage(t('errorAddIncident'));
|
|
return;
|
|
}
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
const newIncident: Incident = {
|
|
incidentId: incidentId,
|
|
title: newIncidentTitle,
|
|
summary: '',
|
|
chapters: [],
|
|
};
|
|
|
|
return {
|
|
...act,
|
|
incidents: [...(act.incidents || []), newIncident],
|
|
};
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
setNewIncidentTitle('');
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(t('errorAddIncident'));
|
|
} else {
|
|
errorMessage(t('errorUnknownAddIncident'));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deleteIncident(actId: number, incidentId: string): Promise<void> {
|
|
try {
|
|
const response: boolean = await System.authDeleteToServer<boolean>('book/incident/remove', {
|
|
bookId,
|
|
incidentId,
|
|
}, token, lang);
|
|
if (!response) {
|
|
errorMessage(t('errorDeleteIncident'));
|
|
return;
|
|
}
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
return {
|
|
...act,
|
|
incidents: (act.incidents || []).filter(
|
|
(inc: Incident): boolean => inc.incidentId !== incidentId,
|
|
),
|
|
};
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t('errorUnknownDeleteIncident'));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function addPlotPoint(actId: number): Promise<void> {
|
|
if (newPlotPointTitle.trim() === '') return;
|
|
try {
|
|
const plotId: string = await System.authPostToServer<string>('book/plot/new', {
|
|
bookId,
|
|
name: newPlotPointTitle,
|
|
incidentId: selectedIncidentId,
|
|
}, token, lang);
|
|
if (!plotId) {
|
|
errorMessage(t('errorAddPlotPoint'));
|
|
return;
|
|
}
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
const newPlotPoint: PlotPoint = {
|
|
plotPointId: plotId,
|
|
title: newPlotPointTitle,
|
|
summary: '',
|
|
linkedIncidentId: selectedIncidentId,
|
|
chapters: [],
|
|
};
|
|
return {
|
|
...act,
|
|
plotPoints: [...(act.plotPoints || []), newPlotPoint],
|
|
};
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
setNewPlotPointTitle('');
|
|
setSelectedIncidentId('');
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(t('errorAddPlotPoint'));
|
|
} else {
|
|
errorMessage(t('errorUnknownAddPlotPoint'));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deletePlotPoint(actId: number, plotPointId: string): Promise<void> {
|
|
try {
|
|
const response: boolean = await System.authDeleteToServer<boolean>('book/plot/remove', {
|
|
plotId: plotPointId,
|
|
}, token, lang);
|
|
if (!response) {
|
|
errorMessage(t('errorDeletePlotPoint'));
|
|
return;
|
|
}
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
return {
|
|
...act,
|
|
plotPoints: (act.plotPoints || []).filter(
|
|
(pp: PlotPoint): boolean => pp.plotPointId !== plotPointId,
|
|
),
|
|
};
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t('errorUnknownDeletePlotPoint'));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function linkChapter(
|
|
actId: number,
|
|
chapterId: string,
|
|
destination: 'act' | 'incident' | 'plotPoint',
|
|
itemId?: string,
|
|
): Promise<void> {
|
|
const chapterToLink: ChapterListProps | undefined = mainChapters.find((chapter: ChapterListProps): boolean => chapter.chapterId === chapterId);
|
|
if (!chapterToLink) {
|
|
errorMessage(t('errorChapterNotFound'));
|
|
return;
|
|
}
|
|
try {
|
|
const linkId: string =
|
|
await System.authPostToServer<string>('chapter/resume/add', {
|
|
bookId,
|
|
chapterId: chapterId,
|
|
actId: actId,
|
|
plotId: destination === 'plotPoint' ? itemId : null,
|
|
incidentId: destination === 'incident' ? itemId : null,
|
|
}, token, lang);
|
|
if (!linkId) {
|
|
errorMessage(t('errorLinkChapter'));
|
|
return;
|
|
}
|
|
const newChapter: ActChapter = {
|
|
chapterInfoId: linkId,
|
|
chapterId: chapterId,
|
|
title: chapterToLink.title,
|
|
chapterOrder: chapterToLink.chapterOrder || 0,
|
|
actId: actId,
|
|
incidentId: destination === 'incident' ? itemId : '0',
|
|
plotPointId: destination === 'plotPoint' ? itemId : '0',
|
|
summary: '',
|
|
goal: '',
|
|
};
|
|
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
switch (destination) {
|
|
case 'act':
|
|
return {
|
|
...act,
|
|
chapters: [...(act.chapters || []), newChapter],
|
|
};
|
|
case 'incident':
|
|
return {
|
|
...act,
|
|
incidents:
|
|
act.incidents?.map((incident: Incident): Incident =>
|
|
incident.incidentId === itemId
|
|
? {
|
|
...incident,
|
|
chapters: [...(incident.chapters || []), newChapter],
|
|
}
|
|
: incident,
|
|
) || [],
|
|
};
|
|
case 'plotPoint':
|
|
return {
|
|
...act,
|
|
plotPoints:
|
|
act.plotPoints?.map(
|
|
(plotPoint: PlotPoint): PlotPoint =>
|
|
plotPoint.plotPointId === itemId
|
|
? {
|
|
...plotPoint,
|
|
chapters: [...(plotPoint.chapters || []), newChapter],
|
|
}
|
|
: plotPoint,
|
|
) || [],
|
|
};
|
|
}
|
|
}
|
|
return act;
|
|
});
|
|
|
|
setActs(updatedActs);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t('errorUnknownLinkChapter'));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function unlinkChapter(
|
|
chapterInfoId: string,
|
|
actId: number,
|
|
chapterId: string,
|
|
destination: 'act' | 'incident' | 'plotPoint',
|
|
itemId?: string,
|
|
): Promise<void> {
|
|
try {
|
|
const response: boolean = await System.authDeleteToServer<boolean>('chapter/resume/remove', {
|
|
chapterInfoId,
|
|
}, token, lang);
|
|
if (!response) {
|
|
errorMessage(t('errorUnlinkChapter'));
|
|
return;
|
|
}
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
switch (destination) {
|
|
case 'act':
|
|
return {
|
|
...act,
|
|
chapters: (act.chapters || []).filter(
|
|
(ch: ActChapter): boolean => ch.chapterId !== chapterId,
|
|
),
|
|
};
|
|
|
|
case 'incident':
|
|
if (!itemId) return act;
|
|
|
|
return {
|
|
...act,
|
|
incidents:
|
|
act.incidents?.map((incident: Incident): Incident => {
|
|
if (incident.incidentId === itemId) {
|
|
return {
|
|
...incident,
|
|
chapters: (incident.chapters || []).filter(
|
|
(ch: ActChapter): boolean =>
|
|
ch.chapterId !== chapterId,
|
|
),
|
|
};
|
|
}
|
|
return incident;
|
|
}) || [],
|
|
};
|
|
|
|
case 'plotPoint':
|
|
if (!itemId) return act;
|
|
|
|
return {
|
|
...act,
|
|
plotPoints:
|
|
act.plotPoints?.map((plotPoint: PlotPoint): PlotPoint => {
|
|
if (plotPoint.plotPointId === itemId) {
|
|
return {
|
|
...plotPoint,
|
|
chapters: (plotPoint.chapters || []).filter((chapter: ActChapter): boolean => chapter.chapterId !== chapterId),
|
|
};
|
|
}
|
|
return plotPoint;
|
|
}) || [],
|
|
};
|
|
}
|
|
}
|
|
return act;
|
|
});
|
|
|
|
setActs(updatedActs);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t('errorUnknownUnlinkChapter'));
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateLinkedChapterSummary(
|
|
actId: number,
|
|
chapterId: string,
|
|
summary: string,
|
|
destination: 'act' | 'incident' | 'plotPoint',
|
|
itemId?: string,
|
|
): void {
|
|
const updatedActs: ActType[] = acts.map((act: ActType): ActType => {
|
|
if (act.id === actId) {
|
|
switch (destination) {
|
|
case 'act':
|
|
return {
|
|
...act,
|
|
chapters: (act.chapters || []).map((chapter: ActChapter): ActChapter => {
|
|
if (chapter.chapterId === chapterId) {
|
|
return {...chapter, summary};
|
|
}
|
|
return chapter;
|
|
}),
|
|
};
|
|
|
|
case 'incident':
|
|
if (!itemId) return act;
|
|
|
|
return {
|
|
...act,
|
|
incidents:
|
|
act.incidents?.map((incident: Incident): Incident => {
|
|
if (incident.incidentId === itemId) {
|
|
return {
|
|
...incident,
|
|
chapters: (incident.chapters || []).map((chapter: ActChapter) => {
|
|
if (chapter.chapterId === chapterId) {
|
|
return {...chapter, summary};
|
|
}
|
|
return chapter;
|
|
}),
|
|
};
|
|
}
|
|
return incident;
|
|
}) || [],
|
|
};
|
|
|
|
case 'plotPoint':
|
|
if (!itemId) return act;
|
|
|
|
return {
|
|
...act,
|
|
plotPoints:
|
|
act.plotPoints?.map((plotPoint: PlotPoint): PlotPoint => {
|
|
if (plotPoint.plotPointId === itemId) {
|
|
return {
|
|
...plotPoint,
|
|
chapters: (plotPoint.chapters || []).map((chapter: ActChapter): ActChapter => {
|
|
if (chapter.chapterId === chapterId) {
|
|
return {...chapter, summary};
|
|
}
|
|
return chapter;
|
|
}),
|
|
};
|
|
}
|
|
return plotPoint;
|
|
}) || [],
|
|
};
|
|
}
|
|
}
|
|
return act;
|
|
});
|
|
setActs(updatedActs);
|
|
}
|
|
|
|
function getSectionKey(actId: number, section: string): string {
|
|
return `section_${actId}_${section}`;
|
|
}
|
|
|
|
|
|
function renderActChapters(act: ActType) {
|
|
if (act.id === 2 || act.id === 3) {
|
|
return null;
|
|
}
|
|
|
|
const sectionKey: string = getSectionKey(act.id, 'chapters');
|
|
const isExpanded: boolean = expandedSections[sectionKey];
|
|
|
|
return (
|
|
<ActChaptersSection
|
|
actId={act.id}
|
|
chapters={act.chapters || []}
|
|
mainChapters={mainChapters}
|
|
onLinkChapter={(actId, chapterId) => linkChapter(actId, chapterId, 'act')}
|
|
onUpdateChapterSummary={(chapterId, summary) =>
|
|
updateLinkedChapterSummary(act.id, chapterId, summary, 'act')
|
|
}
|
|
onUnlinkChapter={(chapterInfoId, chapterId) =>
|
|
unlinkChapter(chapterInfoId, act.id, chapterId, 'act')
|
|
}
|
|
sectionKey={sectionKey}
|
|
isExpanded={isExpanded}
|
|
onToggleSection={toggleSection}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderActDescription(act: ActType) {
|
|
if (act.id === 2 || act.id === 3) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<ActDescription
|
|
actId={act.id}
|
|
summary={act.summary || ''}
|
|
onUpdateSummary={updateActSummary}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderIncidents(act: ActType) {
|
|
if (act.id !== 2) return null;
|
|
|
|
const sectionKey: string = getSectionKey(act.id, 'incidents');
|
|
const isExpanded: boolean = expandedSections[sectionKey];
|
|
|
|
return (
|
|
<ActIncidents
|
|
incidents={act.incidents || []}
|
|
actId={act.id}
|
|
mainChapters={mainChapters}
|
|
newIncidentTitle={newIncidentTitle}
|
|
setNewIncidentTitle={setNewIncidentTitle}
|
|
onAddIncident={addIncident}
|
|
onDeleteIncident={deleteIncident}
|
|
onLinkChapter={(actId, chapterId, incidentId) =>
|
|
linkChapter(actId, chapterId, 'incident', incidentId)
|
|
}
|
|
onUpdateChapterSummary={(chapterId, summary, incidentId) =>
|
|
updateLinkedChapterSummary(act.id, chapterId, summary, 'incident', incidentId)
|
|
}
|
|
onUnlinkChapter={(chapterInfoId, chapterId, incidentId) =>
|
|
unlinkChapter(chapterInfoId, act.id, chapterId, 'incident', incidentId)
|
|
}
|
|
sectionKey={sectionKey}
|
|
isExpanded={isExpanded}
|
|
onToggleSection={toggleSection}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderPlotPoints(act: ActType) {
|
|
if (act.id !== 3) return null;
|
|
|
|
const sectionKey: string = getSectionKey(act.id, 'plotPoints');
|
|
const isExpanded: boolean = expandedSections[sectionKey];
|
|
|
|
return (
|
|
<ActPlotPoints
|
|
plotPoints={act.plotPoints || []}
|
|
incidents={getIncidents()}
|
|
actId={act.id}
|
|
mainChapters={mainChapters}
|
|
newPlotPointTitle={newPlotPointTitle}
|
|
setNewPlotPointTitle={setNewPlotPointTitle}
|
|
selectedIncidentId={selectedIncidentId}
|
|
setSelectedIncidentId={setSelectedIncidentId}
|
|
onAddPlotPoint={addPlotPoint}
|
|
onDeletePlotPoint={deletePlotPoint}
|
|
onLinkChapter={(actId, chapterId, plotPointId) =>
|
|
linkChapter(actId, chapterId, 'plotPoint', plotPointId)
|
|
}
|
|
onUpdateChapterSummary={(chapterId, summary, plotPointId) =>
|
|
updateLinkedChapterSummary(act.id, chapterId, summary, 'plotPoint', plotPointId)
|
|
}
|
|
onUnlinkChapter={(chapterInfoId, chapterId, plotPointId) =>
|
|
unlinkChapter(chapterInfoId, act.id, chapterId, 'plotPoint', plotPointId)
|
|
}
|
|
sectionKey={sectionKey}
|
|
isExpanded={isExpanded}
|
|
onToggleSection={toggleSection}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function renderActIcon(actId: number): IconDefinition {
|
|
switch (actId) {
|
|
case 1:
|
|
return faFlag;
|
|
case 2:
|
|
return faFire;
|
|
case 3:
|
|
return faPuzzlePiece;
|
|
case 4:
|
|
return faScaleBalanced;
|
|
case 5:
|
|
return faTrophy;
|
|
default:
|
|
return faFlag;
|
|
}
|
|
}
|
|
|
|
function renderActTitle(actId: number): string {
|
|
switch (actId) {
|
|
case 1:
|
|
return t('act1Title');
|
|
case 2:
|
|
return t('act2Title');
|
|
case 3:
|
|
return t('act3Title');
|
|
case 4:
|
|
return t('act4Title');
|
|
case 5:
|
|
return t('act5Title');
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{acts.map((act: ActType) => (
|
|
<CollapsableArea key={`act-${act.id}`}
|
|
title={renderActTitle(act.id)}
|
|
icon={renderActIcon(act.id)}
|
|
children={
|
|
<>
|
|
{renderActDescription(act)}
|
|
{renderActChapters(act)}
|
|
{renderIncidents(act)}
|
|
{renderPlotPoints(act)}
|
|
</>
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
} |