'use client' import {useCallback, useContext, useEffect, useState} from 'react'; import { initialSpellState, SpellEditState, SpellListItem, SpellListResponse, SpellProps, SpellTagProps } from '@/lib/models/Spell'; import {SeriesSpellDetailResponse, SeriesSpellListItem, SeriesSpellListResponse} from '@/lib/models/Series'; import {SessionContext} from '@/context/SessionContext'; import {BookContext} from '@/context/BookContext'; import {AlertContext} from '@/context/AlertContext'; import {LangContext, LangContextProps} from '@/context/LangContext'; import System from '@/lib/models/System'; import {useTranslations} from 'next-intl'; import {ViewMode} from '@/shared/interface'; 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 {SeriesContext, SeriesContextProps} from '@/context/SeriesContext'; import {SeriesSyncContext, SeriesSyncContextProps} from '@/context/SeriesSyncContext'; import {SyncedSeries} from '@/lib/models/SyncedSeries'; export interface UseSpellsConfig { entityType: 'book' | 'series'; entityId: string; } export interface UseSpellsReturn { spells: SpellListItem[]; seriesSpells: SeriesSpellListItem[]; tags: SpellTagProps[]; selectedSpell: SpellEditState | null; selectedSeriesSpell: SeriesSpellDetailResponse | null; toolEnabled: boolean; isLoading: boolean; isSeriesMode: boolean; bookSeriesId: string | null; showTagManager: boolean; viewMode: ViewMode; spellBackup: SpellEditState | null; selectSpell: (spell: SpellListItem) => Promise; addNewSpell: () => void; clearSelection: () => void; saveSpell: () => Promise; deleteSpell: (spellId: string) => Promise; updateSpellField: (key: keyof SpellEditState, value: string | string[] | null) => void; toggleTool: (enabled: boolean) => Promise; importFromSeries: (seriesSpellId: string) => Promise; exportToSeries: () => Promise; refreshSeriesSpells: () => Promise; setSelectedSpell: React.Dispatch>; setShowTagManager: (show: boolean) => void; enterDetailMode: (spell: SpellListItem) => Promise; enterEditMode: () => void; exitEditMode: (save: boolean) => Promise; backToList: () => void; createTag: (name: string, color: string) => Promise; updateTag: (tagId: string, name: string, color: string) => Promise; deleteTag: (tagId: string) => Promise; handleSyncComplete: () => Promise; } export function useSpells(config: UseSpellsConfig): UseSpellsReturn { const {entityType, entityId} = config; const t = useTranslations(); const {lang} = useContext(LangContext); const {session} = useContext(SessionContext); const {book, setBook} = useContext(BookContext); const {errorMessage, successMessage} = useContext(AlertContext); const {isCurrentlyOffline} = useContext(OfflineContext); const {addToQueue} = useContext(LocalSyncQueueContext); const {localSyncedBooks} = useContext(BooksSyncContext); const {localSeries} = useContext(SeriesContext); const {localSyncedSeries} = useContext(SeriesSyncContext); const [spells, setSpells] = useState([]); const [seriesSpells, setSeriesSpells] = useState([]); const [tags, setTags] = useState([]); const [selectedSpell, setSelectedSpell] = useState(null); const [selectedSeriesSpell, setSelectedSeriesSpell] = useState(null); const [toolEnabled, setToolEnabled] = useState(entityType === 'series' || (book?.tools?.spells ?? false)); const [isLoading, setIsLoading] = useState(true); const [showTagManager, setShowTagManager] = useState(false); const [viewMode, setViewMode] = useState('list'); const [spellBackup, setSpellBackup] = useState(null); const isSeriesMode: boolean = entityType === 'series'; const bookSeriesId: string | null = book?.seriesId || null; const userToken: string = session?.accessToken || ''; useEffect(function (): void { if (entityId) { refreshSpells().then(); } }, [entityId]); useEffect(function (): void { if (bookSeriesId && !isSeriesMode) { refreshSeriesSpells().then(); } }, [bookSeriesId, isSeriesMode]); const refreshSeriesSpells = useCallback(async function (): Promise { if (!bookSeriesId) return; try { let response: SeriesSpellListResponse; // Dual logic: offline ou livre local → IPC, sinon serveur if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke( 'db:series:spell:list', {seriesId: bookSeriesId} ); } else { response = await System.authGetQueryToServer( 'series/spell/list', userToken, lang, {seriesid: bookSeriesId} ); } if (response) { setSeriesSpells(response.spells); } } catch (e: unknown) { if (e instanceof Error) { console.error('Error loading series spells:', e.message); } } }, [bookSeriesId, userToken, lang, isCurrentlyOffline, book?.localBook]); const refreshSpells = useCallback(async function (): Promise { setIsLoading(true); try { if (isSeriesMode) { // Series mode - dual logic let response: SeriesSpellListResponse; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke( 'db:series:spell:list', {seriesId: entityId} ); } else { response = await System.authGetQueryToServer( 'series/spell/list', userToken, lang, {seriesid: entityId} ); } if (response) { const mappedSpells: SpellListItem[] = response.spells.map(function (spell: SeriesSpellListItem): SpellListItem { return { id: spell.id, name: spell.name, description: spell.description, tags: spell.tags ? spell.tags.map(function (tagId: string): SpellTagProps { const foundTag: SpellTagProps | undefined = response.tags.find(function (t: SpellTagProps): boolean { return t.id === tagId; }); return foundTag || {id: tagId, name: tagId, color: null}; }) : [], }; }); setSpells(mappedSpells); setTags(response.tags || []); } } else { let response: SpellListResponse; if (isCurrentlyOffline()) { response = await window.electron.invoke('db:spell:list', {bookid: entityId}); } else if (book?.localBook) { response = await window.electron.invoke('db:spell:list', {bookid: entityId}); } else { response = await System.authGetQueryToServer( 'spell/list', userToken, lang, {bookid: entityId} ); } if (response) { setSpells(response.spells.map(function (spell: SpellListItem): SpellListItem { return { ...spell, tags: spell.tags || [] }; })); 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")); } } finally { setIsLoading(false); } }, [entityId, isSeriesMode, userToken, lang, book, setBook, errorMessage, t, isCurrentlyOffline]); const selectSpell = useCallback(async function (spell: SpellListItem): Promise { const tagIds: string[] = spell.tags ? spell.tags.map(function (tag: SpellTagProps): string { return tag.id; }) : []; setSelectedSpell({ id: spell.id, name: spell.name, description: spell.description, appearance: '', tags: tagIds, powerLevel: null, components: null, limitations: null, notes: null, seriesSpellId: spell.seriesSpellId || null, }); setSelectedSeriesSpell(null); try { if (isSeriesMode) { // Series mode - dual logic let response: SeriesSpellDetailResponse; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke( 'db:series:spell:detail', {spellId: spell.id} ); } else { response = await System.authGetQueryToServer( 'series/spell/detail', userToken, lang, {spellid: spell.id} ); } if (response) { setSelectedSpell(function (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, }; }); } } else { 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', userToken, lang, {spellid: spell.id} ); } if (response) { setSelectedSpell(function (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, seriesSpellId: response.seriesSpellId || null, }; }); if (response.seriesSpellId) { const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer( 'series/spell/detail', userToken, lang, {spellid: response.seriesSpellId} ); if (seriesSpellResponse) { setSelectedSeriesSpell(seriesSpellResponse); } } } } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } } }, [isSeriesMode, userToken, lang, errorMessage, isCurrentlyOffline, book?.localBook]); const addNewSpell = useCallback(function (): void { setSelectedSpell({...initialSpellState}); setSelectedSeriesSpell(null); setViewMode('edit'); setSpellBackup(null); }, []); const clearSelection = useCallback(function (): void { setSelectedSpell(null); setSelectedSeriesSpell(null); setViewMode('list'); setSpellBackup(null); }, []); const updateSpellField = useCallback(function (key: keyof SpellEditState, value: string | string[] | null): void { if (selectedSpell) { setSelectedSpell({...selectedSpell, [key]: value}); } }, [selectedSpell]); const toggleTool = useCallback(async function (enabled: boolean): Promise { if (isSeriesMode) return; try { const requestData = { bookId: book?.bookId, toolName: 'spells', enabled: enabled }; let response: boolean; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:tool:update', requestData); } else { response = await System.authPatchToServer('book/tool-setting', requestData, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) { addToQueue('db:book:tool:update', requestData); } } 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); } } }, [isSeriesMode, book, setBook, userToken, lang, errorMessage, isCurrentlyOffline, localSyncedBooks, addToQueue]); const saveSpell = useCallback(async function (): Promise { if (!selectedSpell) return false; if (selectedSpell.id === null) { return await addSpellInternal(selectedSpell); } else { return await updateSpellInternal(selectedSpell); } }, [selectedSpell]); async function addSpellInternal(spell: SpellEditState): Promise { if (!spell.name) { errorMessage(t("spellComponent.errorNameRequired")); return false; } try { let newSpellId: string; if (isSeriesMode) { // Series mode - dual logic const data = { seriesId: entityId, name: spell.name, description: spell.description, appearance: spell.appearance, tags: spell.tags, powerLevel: spell.powerLevel, components: spell.components, limitations: spell.limitations, notes: spell.notes, }; if (isCurrentlyOffline() || localSeries) { newSpellId = await window.electron.invoke('db:series:spell:add', data); } else { newSpellId = await System.authPostToServer('series/spell/add', data, userToken, lang); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:add', {...data, id: newSpellId}); } } } else { const data = { bookId: entityId, spell: { name: spell.name, description: spell.description, appearance: spell.appearance, tags: spell.tags, powerLevel: spell.powerLevel, components: spell.components, limitations: spell.limitations, notes: spell.notes, } }; if (isCurrentlyOffline() || book?.localBook) { newSpellId = await window.electron.invoke('db:spell:create', data); } else { newSpellId = await System.authPostToServer('spell/add', data, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:create', {...data, id: newSpellId}); } } } if (!newSpellId) { errorMessage(t("spellComponent.errorAddSpell")); return false; } const resolvedTags: SpellTagProps[] = tags.filter(function (tag: SpellTagProps): boolean { return spell.tags.includes(tag.id); }); const newSpellListItem: SpellListItem = { id: newSpellId, name: spell.name, description: spell.description.length > 150 ? spell.description.substring(0, 150) + '...' : spell.description, tags: resolvedTags, }; setSpells(function (prev: SpellListItem[]): SpellListItem[] { return [...prev, newSpellListItem]; }); setSelectedSpell(null); return true; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } return false; } } async function updateSpellInternal(spellToUpdate: SpellEditState): Promise { if (!spellToUpdate.id) return false; if (!spellToUpdate.name) { errorMessage(t("spellComponent.errorNameRequired")); return false; } try { let success: boolean; const data = { id: spellToUpdate.id, name: spellToUpdate.name, description: spellToUpdate.description, appearance: spellToUpdate.appearance, tags: spellToUpdate.tags, powerLevel: spellToUpdate.powerLevel, components: spellToUpdate.components, limitations: spellToUpdate.limitations, notes: spellToUpdate.notes, }; if (isSeriesMode) { // Series mode - dual logic if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:update', data); } else { success = await System.authPutToServer('series/spell/update', data, userToken, lang); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:update', data); } } } else { if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:update', data); } else { success = await System.authPutToServer('spell/update', data, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:update', data); } } } if (!success) { errorMessage(t("spellComponent.errorUpdateSpell")); return false; } const resolvedTags: SpellTagProps[] = tags.filter(function (tag: SpellTagProps): boolean { return spellToUpdate.tags.includes(tag.id); }); setSpells(function (prev: SpellListItem[]): SpellListItem[] { return prev.map(function (spell: SpellListItem): SpellListItem { return spell.id === spellToUpdate.id ? { id: spellToUpdate.id, name: spellToUpdate.name, description: spellToUpdate.description.length > 150 ? spellToUpdate.description.substring(0, 150) + '...' : spellToUpdate.description, tags: resolvedTags, } : spell; }); }); setSelectedSpell(null); successMessage(t("spellComponent.successUpdate")); return true; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } return false; } } const deleteSpell = useCallback(async function (spellId: string): Promise { try { let success: boolean; const requestData = {spellId}; if (isSeriesMode) { // Series mode - dual logic if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:delete', requestData); } else { success = await System.authDeleteToServer('series/spell/delete', requestData, userToken, lang); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:delete', requestData); } } } else { if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:delete', requestData); } else { success = await System.authDeleteToServer('spell/delete', requestData, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:delete', requestData); } } } if (!success) { errorMessage(t("spellComponent.errorDeleteSpell")); return; } setSpells(function (prev: SpellListItem[]): SpellListItem[] { return prev.filter(function (s: SpellListItem): boolean { return s.id !== spellId; }); }); setSelectedSpell(null); } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t("common.unknownError")); } } }, [isSeriesMode, userToken, lang, errorMessage, t, isCurrentlyOffline, book?.localBook, entityId, localSyncedBooks, addToQueue]); const exportToSeries = useCallback(async function (): Promise { if (!selectedSpell || !selectedSpell.id || !bookSeriesId) return; try { const seriesSpellData = { seriesId: bookSeriesId, name: selectedSpell.name, description: selectedSpell.description, appearance: selectedSpell.appearance || '', tags: [], powerLevel: selectedSpell.powerLevel || null, components: selectedSpell.components || null, limitations: selectedSpell.limitations || null, notes: selectedSpell.notes || null, }; let seriesSpellId: string; if (isCurrentlyOffline() || book?.localBook) { // Mode offline ou livre local → IPC seriesSpellId = await window.electron.invoke('db:series:spell:add', seriesSpellData); } else { // Mode online → Serveur seriesSpellId = await System.authPostToServer( 'series/spell/add', seriesSpellData, userToken, lang ); // Si la série a une copie locale → addToQueue if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === bookSeriesId)) { addToQueue('db:series:spell:add', {...seriesSpellData, id: seriesSpellId}); } } if (seriesSpellId) { const updateData = { id: selectedSpell.id, name: selectedSpell.name, description: selectedSpell.description, appearance: selectedSpell.appearance, tags: selectedSpell.tags, powerLevel: selectedSpell.powerLevel, components: selectedSpell.components, limitations: selectedSpell.limitations, notes: selectedSpell.notes, seriesSpellId: seriesSpellId }; let updateSuccess: boolean; if (isCurrentlyOffline() || book?.localBook) { // Mode offline ou livre local → IPC updateSuccess = await window.electron.invoke('db:spell:update', updateData); } else { // Mode online → Serveur updateSuccess = await System.authPutToServer('spell/update', updateData, userToken, lang); // Si le livre a une copie locale → addToQueue if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:update', updateData); } } if (updateSuccess) { setSelectedSpell({...selectedSpell, seriesSpellId: seriesSpellId}); setSpells(function (prev: SpellListItem[]): SpellListItem[] { return prev.map(function (s: SpellListItem): SpellListItem { return s.id === selectedSpell.id ? {...s, seriesSpellId: seriesSpellId} : s; }); }); const newSeriesSpell: SeriesSpellListItem = { id: seriesSpellId, name: selectedSpell.name, description: selectedSpell.description.length > 150 ? selectedSpell.description.substring(0, 150) + '...' : selectedSpell.description, tags: null, }; setSeriesSpells(function (prev: SeriesSpellListItem[]): SeriesSpellListItem[] { return [...prev, newSeriesSpell]; }); successMessage(t("spellComponent.exportSuccess")); } } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } } }, [selectedSpell, bookSeriesId, userToken, lang, successMessage, errorMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks, localSyncedSeries, entityId]); const importFromSeries = useCallback(async function (seriesSpellId: string): Promise { try { // 1. Récupérer les détails du sort de la série let seriesSpellDetail: SeriesSpellDetailResponse; if (isCurrentlyOffline() || book?.localBook) { // Mode offline → IPC pour récupérer les détails du sort de la série locale seriesSpellDetail = await window.electron.invoke( 'db:series:spell:detail', {spellId: seriesSpellId} ); } else { // Mode online → Serveur seriesSpellDetail = await System.authGetQueryToServer( 'series/spell/detail', userToken, lang, {spellid: seriesSpellId} ); } if (!seriesSpellDetail) return; // 2. Créer le sort dans le livre const spellData = { bookId: entityId, spell: { name: seriesSpellDetail.name, description: seriesSpellDetail.description, appearance: seriesSpellDetail.appearance || '', tags: [], powerLevel: seriesSpellDetail.powerLevel, components: seriesSpellDetail.components, limitations: seriesSpellDetail.limitations, notes: seriesSpellDetail.notes, seriesSpellId: seriesSpellId } }; let createdSpellId: string; if (isCurrentlyOffline() || book?.localBook) { // Mode offline ou livre local → IPC createdSpellId = await window.electron.invoke('db:spell:create', spellData); } else { // Mode online → Serveur createdSpellId = await System.authPostToServer('spell/add', spellData, userToken, lang); // Si le livre a une copie locale → addToQueue if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:create', {...spellData, id: createdSpellId}); } } if (createdSpellId) { const newSpellListItem: SpellListItem = { id: createdSpellId, name: seriesSpellDetail.name, description: seriesSpellDetail.description.length > 150 ? seriesSpellDetail.description.substring(0, 150) + '...' : seriesSpellDetail.description, tags: [], seriesSpellId: seriesSpellId, }; setSpells(function (prev: SpellListItem[]): SpellListItem[] { return [...prev, newSpellListItem]; }); } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } } }, [entityId, userToken, lang, errorMessage, successMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks]); const createTag = useCallback(async function (name: string, color: string): Promise { try { if (isSeriesMode) { // Series mode - dual logic const addData = { seriesId: entityId, name: name, color: color, }; let tagId: string; if (isCurrentlyOffline() || localSeries) { tagId = await window.electron.invoke('db:series:spell:tag:add', addData); } else { tagId = await System.authPostToServer( 'series/spell/tag/add', addData, userToken, lang ); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:tag:add', {...addData, id: tagId}); } } if (tagId) { const newTag: SpellTagProps = {id: tagId, name: name, color: color}; setTags(function (prev: SpellTagProps[]): SpellTagProps[] { return [...prev, newTag]; }); return newTag; } return null; } else { const requestData = { bookId: entityId, name: name, color: color, }; let newTag: SpellTagProps; if (isCurrentlyOffline() || book?.localBook) { newTag = await window.electron.invoke('db:spell:tag:create', requestData); } else { newTag = await System.authPostToServer('spell/tag/add', requestData, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:tag:create', {...requestData, id: newTag?.id}); } } if (newTag && newTag.id) { setTags(function (prev: SpellTagProps[]): SpellTagProps[] { return [...prev, newTag]; }); return newTag; } return null; } } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return null; } }, [isSeriesMode, entityId, userToken, lang, errorMessage, isCurrentlyOffline, book?.localBook, localSyncedBooks, addToQueue]); const updateTag = useCallback(async function (tagId: string, name: string, color: string): Promise { try { let success: boolean; const requestData = {tagId, name, color}; if (isSeriesMode) { // Series mode - dual logic if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:tag:update', requestData); } else { success = await System.authPutToServer('series/spell/tag/update', requestData, userToken, lang); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:tag:update', requestData); } } } else { if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:tag:update', requestData); } else { success = await System.authPutToServer('spell/tag/update', requestData, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:tag:update', requestData); } } } if (!success) { errorMessage(t("spellComponent.updateSuccess")); return false; } setTags(function (prev: SpellTagProps[]): SpellTagProps[] { return prev.map(function (tag: SpellTagProps): SpellTagProps { return tag.id === tagId ? {id: tagId, name: name, color: color} : tag; }); }); return true; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return false; } }, [isSeriesMode, userToken, lang, errorMessage, t, isCurrentlyOffline, book?.localBook, entityId, localSyncedBooks, addToQueue]); const deleteTag = useCallback(async function (tagId: string): Promise { try { let success: boolean; if (isSeriesMode) { // Series mode - dual logic const deleteData = {tagId}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:tag:delete', deleteData); } else { success = await System.authDeleteToServer('series/spell/tag/delete', deleteData, userToken, lang); if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) { addToQueue('db:series:spell:tag:delete', deleteData); } } } else { const requestData = {tagId, bookId: entityId}; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:tag:delete', requestData); } else { success = await System.authDeleteToServer('spell/tag/delete', requestData, userToken, lang); if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) { addToQueue('db:spell:tag:delete', requestData); } } } if (success) { setTags(function (prev: SpellTagProps[]): SpellTagProps[] { return prev.filter(function (tag: SpellTagProps): boolean { return tag.id !== tagId; }); }); return true; } return false; } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } return false; } }, [isSeriesMode, entityId, userToken, lang, errorMessage, isCurrentlyOffline, book?.localBook, localSyncedBooks, addToQueue]); const handleSyncComplete = useCallback(async function (): Promise { if (selectedSpell?.seriesSpellId) { const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer( 'series/spell/detail', userToken, lang, {spellid: selectedSpell.seriesSpellId} ); if (seriesSpellResponse) { setSelectedSeriesSpell(seriesSpellResponse); } } }, [selectedSpell?.seriesSpellId, userToken, lang]); const enterDetailMode = useCallback(async function (spell: SpellListItem): Promise { await selectSpell(spell); setViewMode('detail'); setSpellBackup(null); }, [selectSpell]); const enterEditMode = useCallback(function (): void { if (selectedSpell) { setSpellBackup({...selectedSpell}); } setViewMode('edit'); }, [selectedSpell]); const exitEditMode = useCallback(async function (save: boolean): Promise { if (save) { const success: boolean = await saveSpell(); if (!success) return; if (spellBackup) { setViewMode('detail'); } else { setViewMode('list'); } } else { if (spellBackup) { setSelectedSpell(spellBackup); setViewMode('detail'); } else { setSelectedSpell(null); setViewMode('list'); } } setSpellBackup(null); }, [saveSpell, spellBackup]); const backToList = useCallback(function (): void { setSelectedSpell(null); setSelectedSeriesSpell(null); setSpellBackup(null); setViewMode('list'); }, []); return { spells, seriesSpells, tags, selectedSpell, selectedSeriesSpell, toolEnabled, isLoading, isSeriesMode, bookSeriesId, showTagManager, viewMode, spellBackup, selectSpell, addNewSpell, clearSelection, saveSpell, deleteSpell, updateSpellField, toggleTool, importFromSeries, exportToSeries, refreshSeriesSpells, setSelectedSpell, setShowTagManager, enterDetailMode, enterEditMode, exitEditMode, backToList, createTag, updateTag, deleteTag, handleSyncComplete, }; }