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"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; interface ActProps { acts: ActType[]; setActs: Dispatch>; mainChapters: ChapterListProps[]; } export default function Act({acts, setActs, mainChapters}: ActProps) { const t = useTranslations('actComponent'); const {lang} = useContext(LangContext); const {isCurrentlyOffline} = useContext(OfflineContext); 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(''); const [newPlotPointTitle, setNewPlotPointTitle] = useState(''); const [selectedIncidentId, setSelectedIncidentId] = useState(''); 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 { if (newIncidentTitle.trim() === '') return; try { let incidentId: string; if (isCurrentlyOffline()) { incidentId = await window.electron.invoke('db:book:incident:add', { bookId, name: newIncidentTitle, }); } else { if (book?.localBook) { incidentId = await window.electron.invoke('db:book:incident:add', { bookId, name: newIncidentTitle, }); } else { incidentId = await System.authPostToServer('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 { try { let response: boolean; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:book:incident:remove', { bookId, incidentId, }); } else { if (book?.localBook) { response = await window.electron.invoke('db:book:incident:remove', { bookId, incidentId, }); } else { response = await System.authDeleteToServer('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 { if (newPlotPointTitle.trim() === '') return; try { let plotId: string; if (isCurrentlyOffline()) { plotId = await window.electron.invoke('db:book:plot:add', { bookId, name: newPlotPointTitle, incidentId: selectedIncidentId, }); } else { if (book?.localBook) { plotId = await window.electron.invoke('db:book:plot:add', { bookId, name: newPlotPointTitle, incidentId: selectedIncidentId, }); } else { plotId = await System.authPostToServer('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 { try { let response: boolean; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:book:plot:remove', { plotId: plotPointId, }); } else { if (book?.localBook) { response = await window.electron.invoke('db:book:plot:remove', { plotId: plotPointId, }); } else { response = await System.authDeleteToServer('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 { const chapterToLink: ChapterListProps | undefined = mainChapters.find((chapter: ChapterListProps): boolean => chapter.chapterId === chapterId); if (!chapterToLink) { errorMessage(t('errorChapterNotFound')); return; } try { let linkId: string; const linkData = { bookId, chapterId: chapterId, actId: actId, plotId: destination === 'plotPoint' ? itemId : null, incidentId: destination === 'incident' ? itemId : null, }; if (isCurrentlyOffline()) { linkId = await window.electron.invoke('db:chapter:information:add', linkData); } else { if (book?.localBook) { linkId = await window.electron.invoke('db:chapter:information:add', linkData); } else { linkId = await System.authPostToServer('chapter/resume/add', linkData, 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 { try { let response: boolean; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:chapter:information:remove', { chapterInfoId, }); } else { if (book?.localBook) { response = await window.electron.invoke('db:chapter:information:remove', { chapterInfoId, }); } else { response = await System.authDeleteToServer('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 ( 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 ( ); } function renderIncidents(act: ActType) { if (act.id !== 2) return null; const sectionKey: string = getSectionKey(act.id, 'incidents'); const isExpanded: boolean = expandedSections[sectionKey]; return ( 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 ( 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 (
{acts.map((act: ActType) => ( {renderActDescription(act)} {renderActChapters(act)} {renderIncidents(act)} {renderPlotPoints(act)} } /> ))}
); }