'use client'; import React, {forwardRef, useContext, useEffect, useImperativeHandle, useState} from 'react'; import { initialSpellState, SpellEditState, SpellListItem, SpellListResponse, SpellProps, SpellPropsPost, SpellTagProps } from "@/lib/models/Spell"; import {SessionContext} from "@/context/SessionContext"; import {AlertContext} from "@/context/AlertContext"; import {BookContext} from "@/context/BookContext"; import {LangContext, LangContextProps} from "@/context/LangContext"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import {LocalSyncQueueContext, LocalSyncQueueContextProps} from "@/context/SyncQueueContext"; import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext"; import {SyncedBook} from "@/lib/models/SyncedBook"; import System from '@/lib/models/System'; import {useTranslations} from "next-intl"; import ToggleSwitch from "@/components/form/ToggleSwitch"; import InputField from "@/components/form/InputField"; import {faToggleOn} from "@fortawesome/free-solid-svg-icons"; import SpellList from "@/components/book/settings/spells/SpellList"; import SpellDetail from "@/components/book/settings/spells/SpellDetail"; import SpellTagManager from "@/components/book/settings/spells/SpellTagManager"; interface SpellComponentProps { showToggle?: boolean; } export function SpellComponent(props: SpellComponentProps, ref: React.Ref<{ handleSave: () => Promise }>) { const {showToggle = true} = props; const t = useTranslations(); const {lang} = useContext(LangContext); const {isCurrentlyOffline} = useContext(OfflineContext); const {addToQueue} = useContext(LocalSyncQueueContext); const {localSyncedBooks} = useContext(BooksSyncContext); const {session} = useContext(SessionContext); const {book, setBook} = useContext(BookContext); const {errorMessage, successMessage} = useContext(AlertContext); const bookId: string | undefined = book?.bookId; const token: string = session.accessToken; const [spells, setSpells] = useState([]); const [tags, setTags] = useState([]); const [selectedSpell, setSelectedSpell] = useState(null); const [toolEnabled, setToolEnabled] = useState(book?.tools?.spells ?? false); const [showTagManager, setShowTagManager] = useState(false); useImperativeHandle(ref, function () { return { handleSave: handleSaveSpell, }; }); useEffect((): void => { getSpells().then(); }, []); async function handleToggleTool(enabled: boolean): Promise { try { let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:tool:update', { bookId: bookId, toolName: 'spells', enabled: enabled }); } else { response = await System.authPatchToServer('book/tool-setting', { bookId: bookId, toolName: 'spells', enabled: enabled }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:book:tool:update', { bookId: bookId, toolName: 'spells', enabled: enabled }); } } if (response && setBook && book) { setToolEnabled(enabled); setBook({ ...book, tools: { characters: book.tools?.characters ?? false, worlds: book.tools?.worlds ?? false, locations: book.tools?.locations ?? false, spells: enabled } }); } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } } } async function getSpells(): Promise { try { let response: SpellListResponse; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:spell:list', {bookid: bookId}); } else { if (book?.localBook) { response = await window.electron.invoke('db:spell:list', {bookid: bookId}); } else { response = await System.authGetQueryToServer('spell/list', token, lang, { bookid: bookId, }); } } if (response) { setSpells(response.spells); setTags(response.tags); setToolEnabled(response.enabled); if (setBook && book) { setBook({ ...book, tools: { characters: book.tools?.characters ?? false, worlds: book.tools?.worlds ?? false, locations: book.tools?.locations ?? false, spells: response.enabled } }); } } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } } } async function handleSpellClick(spell: SpellListItem): Promise { // Convertir les tags de SpellTagProps[] vers string[] (IDs) const tagIds: string[] = spell.tags.map((tag: SpellTagProps): string => tag.id); // D'abord afficher avec les données de la liste setSelectedSpell({ id: spell.id, name: spell.name, description: spell.description, appearance: '', tags: tagIds, powerLevel: null, components: null, limitations: null, notes: null, }); try { let response: SpellProps; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:spell:detail', {spellid: spell.id}); } else { if (book?.localBook) { response = await window.electron.invoke('db:spell:detail', {spellid: spell.id}); } else { response = await System.authGetQueryToServer('spell/detail', token, lang, { spellid: spell.id, }); } } if (response) { setSelectedSpell((prev: SpellEditState | null): SpellEditState | null => { if (!prev) return null; return { ...prev, appearance: response.appearance, powerLevel: response.powerLevel, components: response.components, limitations: response.limitations, notes: response.notes, // Garder les tags de la liste, pas ceux de l'API }; }); } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } } } function handleAddSpell(): void { setSelectedSpell({...initialSpellState}); } function handleSpellChange(key: keyof SpellEditState, value: string | string[] | null): void { if (selectedSpell) { setSelectedSpell({...selectedSpell, [key]: value}); } } async function handleSaveSpell(): Promise { if (selectedSpell) { if (selectedSpell.id === null) { await addNewSpell(selectedSpell); } else { await updateSpell(selectedSpell); } } } async function addNewSpell(spell: SpellEditState): Promise { if (!spell.name) { errorMessage(t("spellComponent.errorNameRequired")); return; } if (!spell.description) { errorMessage(t("spellComponent.errorDescriptionRequired")); return; } if (!spell.appearance) { errorMessage(t("spellComponent.errorAppearanceRequired")); return; } try { const spellPost: SpellPropsPost = { name: spell.name, description: spell.description, appearance: spell.appearance, tags: spell.tags, powerLevel: spell.powerLevel, components: spell.components, limitations: spell.limitations, notes: spell.notes, }; let spellId: string; if (isCurrentlyOffline() || book?.localBook) { spellId = await window.electron.invoke('db:spell:create', { bookId: bookId, spell: spellPost, }); } else { const createdSpell: SpellProps = await System.authPostToServer('spell/add', { bookId: bookId, spell: spellPost, }, token, lang); if (!createdSpell || !createdSpell.id) { errorMessage(t("spellComponent.errorAddSpell")); return; } spellId = createdSpell.id; if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:create', { bookId: bookId, spell: {...spellPost, id: spellId}, }); } } if (!spellId) { errorMessage(t("spellComponent.errorAddSpell")); return; } // Ajouter à la liste avec les tags résolus const resolvedTags: SpellTagProps[] = tags.filter((tag: SpellTagProps) => spell.tags.includes(tag.id)); const newSpellListItem: SpellListItem = { id: spellId, name: spell.name, description: spell.description.length > 150 ? spell.description.substring(0, 150) + '...' : spell.description, tags: resolvedTags, }; setSpells([...spells, newSpellListItem]); setSelectedSpell(null); successMessage(t("spellComponent.successAdd")); } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } } } async function updateSpell(spell: SpellEditState): Promise { if (!spell.id) return; if (!spell.name) { errorMessage(t("spellComponent.errorNameRequired")); return; } if (!spell.description) { errorMessage(t("spellComponent.errorDescriptionRequired")); return; } if (!spell.appearance) { errorMessage(t("spellComponent.errorAppearanceRequired")); return; } try { const spellPost: SpellPropsPost = { name: spell.name, description: spell.description, appearance: spell.appearance, tags: spell.tags, powerLevel: spell.powerLevel, components: spell.components, limitations: spell.limitations, notes: spell.notes, }; let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:spell:update', { spellId: spell.id, spell: spellPost, }); } else { response = await System.authPutToServer('spell/update', { spellId: spell.id, spell: spellPost, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:update', { spellId: spell.id, spell: spellPost, }); } } if (!response) { errorMessage(t("spellComponent.errorUpdateSpell")); return; } // Mettre à jour la liste avec les tags résolus const resolvedTags: SpellTagProps[] = tags.filter((tag: SpellTagProps) => spell.tags.includes(tag.id)); setSpells(spells.map((s: SpellListItem): SpellListItem => s.id === spell.id ? { id: spell.id, name: spell.name, description: spell.description.length > 150 ? spell.description.substring(0, 150) + '...' : spell.description, tags: resolvedTags, } : s )); setSelectedSpell(null); successMessage(t("spellComponent.successUpdate")); } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } } } async function handleDeleteSpell(spellId: string): Promise { try { let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:spell:delete', { spellId: spellId, }); } else { response = await System.authDeleteToServer('spell/delete', { spellId: spellId, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:delete', { spellId: spellId, }); } } if (!response) { errorMessage(t("spellComponent.errorDeleteSpell")); return; } setSpells(spells.filter((s: SpellListItem) => s.id !== spellId)); setSelectedSpell(null); successMessage(t("spellComponent.successDelete")); } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } } } function handleManageTags(): void { setShowTagManager(true); } function handleBackFromTagManager(): void { setShowTagManager(false); } async function handleCreateTag(name: string, color: string): Promise { try { let tagId: string; if (isCurrentlyOffline() || book?.localBook) { tagId = await window.electron.invoke('db:spell:tag:create', { bookId: bookId, name: name, color: color, }); } else { const newTag: SpellTagProps = await System.authPostToServer('spell/tag/add', { bookId: bookId, name: name, color: color, }, token, lang); if (!newTag || !newTag.id) { return null; } tagId = newTag.id; if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:tag:create', { bookId: bookId, name: name, color: color, tagId: tagId, }); } } if (tagId) { const createdTag: SpellTagProps = {id: tagId, name: name, color: color}; setTags([...tags, createdTag]); return createdTag; } return null; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return null; } } async function handleUpdateTag(tagId: string, name: string, color: string): Promise { try { let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:spell:tag:update', { tagId: tagId, name: name, color: color, }); } else { response = await System.authPutToServer('spell/tag/update', { tagId: tagId, name: name, color: color, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:tag:update', { tagId: tagId, name: name, color: color, }); } } if (response) { setTags(tags.map((tag: SpellTagProps): SpellTagProps => tag.id === tagId ? {id: tagId, name: name, color: color} : tag )); return true; } return false; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return false; } } async function handleDeleteTag(tagId: string): Promise { try { let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:spell:tag:delete', { tagId: tagId, bookId: bookId, }); } else { response = await System.authDeleteToServer('spell/tag/delete', { tagId: tagId, bookId: bookId, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) { addToQueue('db:spell:tag:delete', { tagId: tagId, bookId: bookId, }); } } if (response) { setTags(tags.filter((tag: SpellTagProps): boolean => tag.id !== tagId)); return true; } return false; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return false; } } return (
{showToggle && (
=> handleToggleTool(checked)} /> } />

{t('spellComponent.enableToolDescription')}

)} {toolEnabled && ( <> {showTagManager ? ( ) : selectedSpell ? ( ) : ( )} )}
); } export default forwardRef(SpellComponent);