Files
ERitors-Scribe-Desktop/hooks/settings/useSpells.ts
natreex ee4438834c Migrate from window.electron to tauri IPC functions across components
- Replaced `window.electron.invoke` calls with equivalent `tauri` function calls for all IPC interactions.
- Removed `electron.d.ts` TypeScript definitions as they are no longer needed.
- Updated related logic for offline/online state synchronization.
- Added `types.rs` and `shared/mod.rs` modules to support Tauri IPC integration with Rust enums and shared logic.
- Refactored IPC request queues to use updated handler names for consistency with Tauri.
2026-03-21 09:34:13 -04:00

978 lines
43 KiB
TypeScript

'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';
import * as tauri from '@/lib/tauri';
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<void>;
addNewSpell: () => void;
clearSelection: () => void;
saveSpell: () => Promise<boolean>;
deleteSpell: (spellId: string) => Promise<void>;
updateSpellField: (key: keyof SpellEditState, value: string | string[] | null) => void;
toggleTool: (enabled: boolean) => Promise<void>;
importFromSeries: (seriesSpellId: string) => Promise<void>;
exportToSeries: () => Promise<void>;
refreshSeriesSpells: () => Promise<void>;
setSelectedSpell: React.Dispatch<React.SetStateAction<SpellEditState | null>>;
setShowTagManager: (show: boolean) => void;
enterDetailMode: (spell: SpellListItem) => Promise<void>;
enterEditMode: () => void;
exitEditMode: (save: boolean) => Promise<void>;
backToList: () => void;
createTag: (name: string, color: string) => Promise<SpellTagProps | null>;
updateTag: (tagId: string, name: string, color: string) => Promise<boolean>;
deleteTag: (tagId: string) => Promise<boolean>;
handleSyncComplete: () => Promise<void>;
}
export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
const {entityType, entityId} = config;
const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext);
const {session} = useContext(SessionContext);
const {book, setBook} = useContext(BookContext);
const {errorMessage, successMessage} = useContext(AlertContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
const {localSeries} = useContext<SeriesContextProps>(SeriesContext);
const {localSyncedSeries} = useContext<SeriesSyncContextProps>(SeriesSyncContext);
const [spells, setSpells] = useState<SpellListItem[]>([]);
const [seriesSpells, setSeriesSpells] = useState<SeriesSpellListItem[]>([]);
const [tags, setTags] = useState<SpellTagProps[]>([]);
const [selectedSpell, setSelectedSpell] = useState<SpellEditState | null>(null);
const [selectedSeriesSpell, setSelectedSeriesSpell] = useState<SeriesSpellDetailResponse | null>(null);
const [toolEnabled, setToolEnabled] = useState<boolean>(entityType === 'series' || (book?.tools?.spells ?? false));
const [isLoading, setIsLoading] = useState<boolean>(true);
const [showTagManager, setShowTagManager] = useState<boolean>(false);
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [spellBackup, setSpellBackup] = useState<SpellEditState | null>(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<void> {
if (!bookSeriesId) return;
try {
let response: SeriesSpellListResponse;
// Dual logic: offline ou livre local → IPC, sinon serveur
if (isCurrentlyOffline() || book?.localBook) {
response = await tauri.getSeriesSpellList(bookSeriesId) as SeriesSpellListResponse;
} else {
response = await System.authGetQueryToServer<SeriesSpellListResponse>(
'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<void> {
setIsLoading(true);
try {
if (isSeriesMode) {
// Series mode - dual logic
let response: SeriesSpellListResponse;
if (isCurrentlyOffline() || localSeries) {
response = await tauri.getSeriesSpellList(entityId) as SeriesSpellListResponse;
} else {
response = await System.authGetQueryToServer<SeriesSpellListResponse>(
'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 tauri.getSpellList(entityId, true) as SpellListResponse;
} else if (book?.localBook) {
response = await tauri.getSpellList(entityId, true) as SpellListResponse;
} else {
response = await System.authGetQueryToServer<SpellListResponse>(
'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<void> {
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 tauri.getSeriesSpellDetail(spell.id) as SeriesSpellDetailResponse;
} else {
response = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
'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 tauri.getSpellDetail(spell.id) as SpellProps;
} else if (book?.localBook) {
response = await tauri.getSpellDetail(spell.id) as SpellProps;
} else {
response = await System.authGetQueryToServer<SpellProps>(
'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) {
let seriesSpellResponse: SeriesSpellDetailResponse;
if (isCurrentlyOffline() || book?.localBook) {
seriesSpellResponse = await tauri.getSeriesSpellDetail(response.seriesSpellId) as SeriesSpellDetailResponse;
} else {
seriesSpellResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
'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<void> {
if (isSeriesMode) return;
try {
const requestData = {
bookId: book?.bookId,
toolName: 'spells',
enabled: enabled
};
let response: boolean;
if (isCurrentlyOffline() || book?.localBook) {
response = await tauri.updateBookToolSetting(requestData.bookId, requestData.toolName, requestData.enabled);
} else {
response = await System.authPatchToServer<boolean>('book/tool-setting', requestData, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book?.bookId)) {
addToQueue('update_book_tool_setting', {data: 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<boolean> {
if (!selectedSpell) return false;
if (selectedSpell.id === null) {
return await addSpellInternal(selectedSpell);
} else {
return await updateSpellInternal(selectedSpell);
}
}, [selectedSpell]);
async function addSpellInternal(spell: SpellEditState): Promise<boolean> {
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 tauri.addSeriesSpell(data);
} else {
newSpellId = await System.authPostToServer<string>('series/spell/add', data, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('add_series_spell', {data: {...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 tauri.createSpell(data.bookId, data.spell);
} else {
newSpellId = await System.authPostToServer<string>('spell/add', data, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('create_spell', {data: {...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<boolean> {
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 tauri.updateSeriesSpell(data);
} else {
success = await System.authPutToServer<boolean>('series/spell/update', data, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('update_series_spell', {data});
}
}
} else {
if (isCurrentlyOffline() || book?.localBook) {
success = await tauri.updateSpell(data.id, data);
} else {
success = await System.authPutToServer<boolean>('spell/update', data, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('update_spell', {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<void> {
try {
let success: boolean;
const deletedAt: number = System.timeStampInSeconds();
if (isSeriesMode) {
// Series mode - dual logic
const requestData = {spellId, deletedAt};
if (isCurrentlyOffline() || localSeries) {
success = await tauri.deleteSeriesSpell(requestData.spellId, requestData.deletedAt);
} else {
success = await System.authDeleteToServer<boolean>('series/spell/delete', requestData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('delete_series_spell', {data: requestData});
}
}
} else {
const requestData = {spellId, bookId: entityId, deletedAt};
if (isCurrentlyOffline() || book?.localBook) {
success = await tauri.deleteSpell(requestData.spellId, requestData.bookId, requestData.deletedAt);
} else {
success = await System.authDeleteToServer<boolean>('spell/delete', requestData, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('delete_spell', {data: 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<void> {
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 tauri.addSeriesSpell(seriesSpellData);
} else {
// Mode online → Serveur
seriesSpellId = await System.authPostToServer<string>(
'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('add_series_spell', {data: {...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 → Tauri
updateSuccess = await tauri.updateSpell(updateData.id, updateData);
} else {
// Mode online → Serveur
updateSuccess = await System.authPutToServer<boolean>('spell/update', updateData, userToken, lang);
// Si le livre a une copie locale → addToQueue
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('update_spell', {data: 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<void> {
try {
// 1. Récupérer les détails du sort de la série
let seriesSpellDetail: SeriesSpellDetailResponse;
if (isCurrentlyOffline() || book?.localBook) {
// Mode offline → Tauri pour récupérer les détails du sort de la série locale
seriesSpellDetail = await tauri.getSeriesSpellDetail(seriesSpellId) as SeriesSpellDetailResponse;
} else {
// Mode online → Serveur
seriesSpellDetail = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
'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 → Tauri
createdSpellId = await tauri.createSpell(spellData.bookId, spellData.spell);
} else {
// Mode online → Serveur
createdSpellId = await System.authPostToServer<string>('spell/add', spellData, userToken, lang);
// Si le livre a une copie locale → addToQueue
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('create_spell', {data: {...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<SpellTagProps | null> {
try {
if (isSeriesMode) {
// Series mode - dual logic
const addData = {
seriesId: entityId,
name: name,
color: color,
};
let tagId: string;
if (isCurrentlyOffline() || localSeries) {
tagId = await tauri.addSeriesSpellTag(addData);
} else {
tagId = await System.authPostToServer<string>(
'series/spell/tag/add',
addData,
userToken,
lang
);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('add_series_spell_tag', {data: {...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 tauri.createSpellTag(requestData.bookId, requestData.name, requestData.color) as SpellTagProps;
} else {
newTag = await System.authPostToServer<SpellTagProps>('spell/tag/add', requestData, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('create_spell_tag', {data: {...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<boolean> {
try {
let success: boolean;
const requestData = {tagId, name, color};
if (isSeriesMode) {
// Series mode - dual logic
if (isCurrentlyOffline() || localSeries) {
success = await tauri.updateSeriesSpellTag(requestData);
} else {
success = await System.authPutToServer<boolean>('series/spell/tag/update', requestData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('update_series_spell_tag', {data: requestData});
}
}
} else {
if (isCurrentlyOffline() || book?.localBook) {
success = await tauri.updateSpellTag(requestData.tagId, requestData.name, requestData.color);
} else {
success = await System.authPutToServer<boolean>('spell/tag/update', requestData, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('update_spell_tag', {data: 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<boolean> {
try {
let success: boolean;
const deletedAt: number = System.timeStampInSeconds();
if (isSeriesMode) {
// Series mode - dual logic
const deleteData = {tagId, deletedAt};
if (isCurrentlyOffline() || localSeries) {
success = await tauri.deleteSeriesSpellTag(deleteData.tagId, deleteData.deletedAt);
} else {
success = await System.authDeleteToServer<boolean>('series/spell/tag/delete', deleteData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('delete_series_spell_tag', {data: deleteData});
}
}
} else {
const requestData = {tagId, bookId: entityId, deletedAt};
if (isCurrentlyOffline() || book?.localBook) {
success = await tauri.deleteSpellTag(requestData.tagId, requestData.bookId, requestData.deletedAt);
} else {
success = await System.authDeleteToServer<boolean>('spell/tag/delete', requestData, userToken, lang);
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('delete_spell_tag', {data: 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<void> {
if (selectedSpell?.seriesSpellId) {
let seriesSpellResponse: SeriesSpellDetailResponse;
if (isCurrentlyOffline() || (isSeriesMode ? localSeries : book?.localBook)) {
seriesSpellResponse = await tauri.getSeriesSpellDetail(selectedSpell.seriesSpellId) as SeriesSpellDetailResponse;
} else {
seriesSpellResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
'series/spell/detail',
userToken,
lang,
{spellid: selectedSpell.seriesSpellId}
);
}
if (seriesSpellResponse) {
setSelectedSeriesSpell(seriesSpellResponse);
}
}
}, [selectedSpell?.seriesSpellId, userToken, lang, isCurrentlyOffline, isSeriesMode, localSeries, book?.localBook]);
const enterDetailMode = useCallback(async function (spell: SpellListItem): Promise<void> {
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<void> {
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,
};
}