diff --git a/components/book/settings/characters/CharacterComponent.tsx b/components/book/settings/characters/CharacterComponent.tsx index 4eda4aa..8235e07 100644 --- a/components/book/settings/characters/CharacterComponent.tsx +++ b/components/book/settings/characters/CharacterComponent.tsx @@ -35,12 +35,23 @@ const initialCharacterState: CharacterProps = { id: null, name: '', lastName: '', + nickname: '', + age: '', + gender: '', + species: '', + nationality: '', + status: 'alive', category: 'none', title: '', role: '', image: 'https://via.placeholder.com/150', biography: '', history: '', + speechPattern: '', + catchphrase: '', + residence: '', + notes: '', + color: '', physical: [], psychological: [], relations: [], @@ -49,6 +60,16 @@ const initialCharacterState: CharacterProps = { strengths: [], goals: [], motivations: [], + arc: [], + secrets: [], + fears: [], + flaws: [], + beliefs: [], + conflicts: [], + quotes: [], + distinguishingMarks: [], + items: [], + affiliations: [], }; export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, ref: any) { @@ -102,7 +123,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, setBook({...book, tools: { characters: enabled, worlds: book.tools?.worlds ?? false, - locations: book.tools?.locations ?? false + locations: book.tools?.locations ?? false, + spells: book.tools?.spells ?? false }}); } } catch (e: unknown) { @@ -133,7 +155,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, setBook({...book, tools: { characters: response.enabled, worlds: book.tools?.worlds ?? false, - locations: book.tools?.locations ?? false + locations: book.tools?.locations ?? false, + spells: book.tools?.spells ?? false }}); } } diff --git a/components/book/settings/characters/CharacterDetail.tsx b/components/book/settings/characters/CharacterDetail.tsx index 9feb495..17736cb 100644 --- a/components/book/settings/characters/CharacterDetail.tsx +++ b/components/book/settings/characters/CharacterDetail.tsx @@ -10,23 +10,26 @@ import { CharacterAttribute, characterCategories, CharacterElement, - characterElementCategory, + basicCharacterElements, + advancedCharacterElements, CharacterProps, - characterTitle + characterStatus } from "@/lib/models/Character"; import System from "@/lib/models/System"; import { - faAddressCard, faArrowLeft, faBook, - faLayerGroup, faPlus, faSave, faScroll, - faUser + faUser, + faSliders, + faGlobe, + faCommentDots, + faStickyNote } from "@fortawesome/free-solid-svg-icons"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {Dispatch, SetStateAction, useContext, useEffect} from "react"; +import {Dispatch, SetStateAction, useContext, useEffect, useState} from "react"; import CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement"; import DeleteButton from "@/components/form/DeleteButton"; import {useTranslations} from "next-intl"; @@ -67,6 +70,7 @@ export default function CharacterDetail( const {book} = useContext(BookContext); const {session} = useContext(SessionContext); const {errorMessage} = useContext(AlertContext); + const [showAdvanced, setShowAdvanced] = useState(false); useEffect((): void => { if (selectedCharacter?.id !== null) { @@ -104,13 +108,24 @@ export default function CharacterDetail( setSelectedCharacter({ id: selectedCharacter?.id ?? '', name: selectedCharacter?.name ?? '', - image: selectedCharacter?.image ?? '', lastName: selectedCharacter?.lastName ?? '', + nickname: selectedCharacter?.nickname ?? '', + age: selectedCharacter?.age ?? '', + gender: selectedCharacter?.gender ?? '', + species: selectedCharacter?.species ?? '', + nationality: selectedCharacter?.nationality ?? '', + status: selectedCharacter?.status ?? 'alive', category: selectedCharacter?.category ?? 'none', title: selectedCharacter?.title ?? '', + image: selectedCharacter?.image ?? '', + role: selectedCharacter?.role ?? '', biography: selectedCharacter?.biography, history: selectedCharacter?.history, - role: selectedCharacter?.role ?? '', + speechPattern: selectedCharacter?.speechPattern, + catchphrase: selectedCharacter?.catchphrase, + residence: selectedCharacter?.residence, + notes: selectedCharacter?.notes, + color: selectedCharacter?.color, physical: attributes.physical ?? [], psychological: attributes.psychological ?? [], relations: attributes.relations ?? [], @@ -119,6 +134,16 @@ export default function CharacterDetail( strengths: attributes.strengths ?? [], goals: attributes.goals ?? [], motivations: attributes.motivations ?? [], + arc: attributes.arc ?? [], + secrets: attributes.secrets ?? [], + fears: attributes.fears ?? [], + flaws: attributes.flaws ?? [], + beliefs: attributes.beliefs ?? [], + conflicts: attributes.conflicts ?? [], + quotes: attributes.quotes ?? [], + distinguishingMarks: attributes.distinguishingMarks ?? [], + items: attributes.items ?? [], + affiliations: attributes.affiliations ?? [], }); } } catch (e: unknown) { @@ -173,7 +198,7 @@ export default function CharacterDetail( /> } /> - + } /> - + + handleCharacterChange('nickname', e.target.value)} + placeholder={t("characterDetail.nicknamePlaceholder")} + /> + } + /> + } - icon={faLayerGroup} /> - + handleCharacterChange('title', e.target.value)} - data={characterTitle} + handleCharacterChange('title', e.target.value)} + placeholder={t("characterDetail.titlePlaceholder")} + /> + } + /> + + handleCharacterChange('gender', e.target.value)} + placeholder={t("characterDetail.genderPlaceholder")} + /> + } + /> + + handleCharacterChange('age', e.target.value)} + placeholder={t("characterDetail.agePlaceholder")} /> } - icon={faAddressCard} /> - +
- + - +
- - {characterElementCategory.map((item: CharacterElement, index: number) => ( + + {/* Attributs de base - toujours visibles */} + {basicCharacterElements.map((item: CharacterElement, index: number) => ( ))} + + {/* Toggle Mode Avancé */} +
+
+ + {t("characterDetail.advancedMode")} +
+ +
+ + {/* Sections avancées - visibles uniquement si showAdvanced est true */} + {showAdvanced && ( + <> + {/* Identité étendue */} + +
+ handleCharacterChange('species', e.target.value)} + placeholder={t("characterDetail.speciesPlaceholder")} + /> + } + /> + + handleCharacterChange('nationality', e.target.value)} + placeholder={t("characterDetail.nationalityPlaceholder")} + /> + } + /> + + setSelectedCharacter(prev => + prev ? {...prev, status: e.target.value as CharacterProps['status']} : prev + )} + data={characterStatus} + /> + } + /> + + handleCharacterChange('residence', e.target.value)} + placeholder={t("characterDetail.residencePlaceholder")} + /> + } + /> +
+
+ + {/* Voix du personnage */} + +
+ handleCharacterChange('speechPattern', e.target.value)} + placeholder={t("characterDetail.speechPatternPlaceholder")} + /> + } + /> + + handleCharacterChange('catchphrase', e.target.value)} + placeholder={t("characterDetail.catchphrasePlaceholder")} + /> + } + /> +
+
+ + {/* Notes de l'auteur */} + +
+ handleCharacterChange('notes', e.target.value)} + placeholder={t("characterDetail.notesPlaceholder")} + /> + } + /> + + handleCharacterChange('color', e.target.value)} + placeholder={t("characterDetail.colorPlaceholder")} + /> + } + /> +
+
+ + {/* Attributs avancés */} + {advancedCharacterElements.map((item: CharacterElement, index: number) => ( + + ))} + + )} ); diff --git a/electron/database/models/Character.ts b/electron/database/models/Character.ts index 8a68e2c..a26101a 100644 --- a/electron/database/models/Character.ts +++ b/electron/database/models/Character.ts @@ -13,6 +13,12 @@ export interface CharacterPropsPost { id: string | null; name: string; lastName: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: 'alive' | 'dead' | 'unknown'; category: CharacterCategory; title: string; image: string; @@ -24,9 +30,24 @@ export interface CharacterPropsPost { strengths: { name: string }[]; goals: { name: string }[]; motivations: { name: string }[]; + arc: { name: string }[]; + secrets: { name: string }[]; + fears: { name: string }[]; + flaws: { name: string }[]; + beliefs: { name: string }[]; + conflicts: { name: string }[]; + quotes: { name: string }[]; + distinguishingMarks: { name: string }[]; + items: { name: string }[]; + affiliations: { name: string }[]; role: string; biography?: string; history?: string; + speechPattern?: string; + catchphrase?: string; + residence?: string; + notes?: string; + color?: string; } @@ -34,12 +55,23 @@ export interface CharacterProps { id: string; name: string; lastName: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: string; title: string; category: string; image: string; role: string; biography: string; history: string; + speechPattern: string; + catchphrase: string; + residence: string; + notes: string; + color: string; } export interface CharacterListResponse { @@ -51,12 +83,23 @@ export interface CompleteCharacterProps { id?: string; name: string; lastName: string; + nickname?: string; + age?: string; + gender?: string; + species?: string; + nationality?: string; + status?: string; title: string; category: string; image?: string; role: string; biography: string; history: string; + speechPattern?: string; + catchphrase?: string; + residence?: string; + notes?: string; + color?: string; [key: string]: Attribute[] | string | undefined; } @@ -108,12 +151,23 @@ export default class Character { id: encryptedCharacter.character_id, name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '', lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '', + nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname, userEncryptionKey) : '', + age: encryptedCharacter.age ? System.decryptDataWithUserKey(encryptedCharacter.age, userEncryptionKey) : '', + gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender, userEncryptionKey) : '', + species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species, userEncryptionKey) : '', + nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality, userEncryptionKey) : '', + status: encryptedCharacter.status ? System.decryptDataWithUserKey(encryptedCharacter.status, userEncryptionKey) : 'alive', title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title, userEncryptionKey) : '', category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '', image: encryptedCharacter.image ? System.decryptDataWithUserKey(encryptedCharacter.image, userEncryptionKey) : '', role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '', biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '', history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history, userEncryptionKey) : '', + speechPattern: encryptedCharacter.speech_pattern ? System.decryptDataWithUserKey(encryptedCharacter.speech_pattern, userEncryptionKey) : '', + catchphrase: encryptedCharacter.catchphrase ? System.decryptDataWithUserKey(encryptedCharacter.catchphrase, userEncryptionKey) : '', + residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence, userEncryptionKey) : '', + notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes, userEncryptionKey) : '', + color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color, userEncryptionKey) : '', }) } return { characters: decryptedCharacterList, enabled }; @@ -132,15 +186,30 @@ export default class Character { public static addNewCharacter(userId: string, character: CharacterPropsPost, bookId: string, lang: 'fr' | 'en' = 'fr', existingCharacterId?: string): string { const userEncryptionKey: string = getUserEncryptionKey(userId); const characterId: string = existingCharacterId || System.createUniqueId(); - const encryptedName: string = System.encryptDataWithUserKey(character.name, userEncryptionKey); - const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userEncryptionKey); - const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userEncryptionKey); - const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); - const encryptedImage: string = System.encryptDataWithUserKey(character.image, userEncryptionKey); - const encryptedRole: string = System.encryptDataWithUserKey(character.role, userEncryptionKey); - const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userEncryptionKey); - const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userEncryptionKey); - CharacterRepo.addNewCharacter(userId, characterId, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, bookId, lang); + + const characterData = { + firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey), + lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey), + nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey), + age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey), + gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey), + species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey), + nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey), + status: System.encryptDataWithUserKey(character.status || 'alive', userEncryptionKey), + title: System.encryptDataWithUserKey(character.title, userEncryptionKey), + category: System.encryptDataWithUserKey(character.category, userEncryptionKey), + image: System.encryptDataWithUserKey(character.image, userEncryptionKey), + role: System.encryptDataWithUserKey(character.role, userEncryptionKey), + biography: System.encryptDataWithUserKey(character.biography || '', userEncryptionKey), + history: System.encryptDataWithUserKey(character.history || '', userEncryptionKey), + speechPattern: System.encryptDataWithUserKey(character.speechPattern || '', userEncryptionKey), + catchphrase: System.encryptDataWithUserKey(character.catchphrase || '', userEncryptionKey), + residence: System.encryptDataWithUserKey(character.residence || '', userEncryptionKey), + notes: System.encryptDataWithUserKey(character.notes || '', userEncryptionKey), + color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey), + }; + + CharacterRepo.addNewCharacter(userId, characterId, characterData, bookId, lang); const characterPropertyKeys: string[] = Object.keys(character); for (const propertyKey of characterPropertyKeys) { if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) { @@ -170,15 +239,30 @@ export default class Character { if (!character.id) { return false; } - const encryptedName: string = System.encryptDataWithUserKey(character.name, userEncryptionKey); - const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userEncryptionKey); - const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userEncryptionKey); - const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); - const encryptedImage: string = System.encryptDataWithUserKey(character.image, userEncryptionKey); - const encryptedRole: string = System.encryptDataWithUserKey(character.role, userEncryptionKey); - const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userEncryptionKey); - const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userEncryptionKey); - return CharacterRepo.updateCharacter(userId, character.id, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, System.timeStampInSeconds(), lang); + + const characterData = { + firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey), + lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey), + nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey), + age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey), + gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey), + species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey), + nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey), + status: System.encryptDataWithUserKey(character.status || 'alive', userEncryptionKey), + title: System.encryptDataWithUserKey(character.title, userEncryptionKey), + category: System.encryptDataWithUserKey(character.category, userEncryptionKey), + image: System.encryptDataWithUserKey(character.image, userEncryptionKey), + role: System.encryptDataWithUserKey(character.role, userEncryptionKey), + biography: System.encryptDataWithUserKey(character.biography || '', userEncryptionKey), + history: System.encryptDataWithUserKey(character.history || '', userEncryptionKey), + speechPattern: System.encryptDataWithUserKey(character.speechPattern || '', userEncryptionKey), + catchphrase: System.encryptDataWithUserKey(character.catchphrase || '', userEncryptionKey), + residence: System.encryptDataWithUserKey(character.residence || '', userEncryptionKey), + notes: System.encryptDataWithUserKey(character.notes || '', userEncryptionKey), + color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey), + }; + + return CharacterRepo.updateCharacter(userId, character.id, characterData, System.timeStampInSeconds(), lang); } /** @@ -285,11 +369,22 @@ export default class Character { id: '', name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '', lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '', - title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title, userEncryptionKey) : '', - category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '', - role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '', - biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '', - history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history, userEncryptionKey) : '', + nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname as string, userEncryptionKey) : '', + age: encryptedCharacter.age ? System.decryptDataWithUserKey(encryptedCharacter.age as string, userEncryptionKey) : '', + gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender as string, userEncryptionKey) : '', + species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species as string, userEncryptionKey) : '', + nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality as string, userEncryptionKey) : '', + status: encryptedCharacter.status ? System.decryptDataWithUserKey(encryptedCharacter.status as string, userEncryptionKey) : 'alive', + title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title as string, userEncryptionKey) : '', + category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category as string, userEncryptionKey) : '', + role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role as string, userEncryptionKey) : '', + biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography as string, userEncryptionKey) : '', + history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history as string, userEncryptionKey) : '', + speechPattern: encryptedCharacter.speech_pattern ? System.decryptDataWithUserKey(encryptedCharacter.speech_pattern as string, userEncryptionKey) : '', + catchphrase: encryptedCharacter.catchphrase ? System.decryptDataWithUserKey(encryptedCharacter.catchphrase as string, userEncryptionKey) : '', + residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence as string, userEncryptionKey) : '', + notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes as string, userEncryptionKey) : '', + color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color as string, userEncryptionKey) : '', physical: [], psychological: [], relations: [], @@ -297,7 +392,17 @@ export default class Character { weaknesses: [], strengths: [], goals: [], - motivations: [] + motivations: [], + arc: [], + secrets: [], + fears: [], + flaws: [], + beliefs: [], + conflicts: [], + quotes: [], + distinguishingMarks: [], + items: [], + affiliations: [] }; completeCharactersMap.set(encryptedCharacter.character_id, decryptedCharacter); } diff --git a/electron/database/models/Download.ts b/electron/database/models/Download.ts index c617da7..c2be45a 100644 --- a/electron/database/models/Download.ts +++ b/electron/database/models/Download.ts @@ -106,15 +106,28 @@ export default class Download { if (!chapterInfosInserted) return false; const charactersInserted: boolean = data.characters.every((character: BookCharactersTable): boolean => { - const encryptedCharacterFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey); - const encryptedCharacterLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null; - const encryptedCharacterCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); - const encryptedCharacterTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null; - const encryptedCharacterImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null; - const encryptedCharacterRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null; - const encryptedCharacterBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null; - const encryptedCharacterHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null; - return CharacterRepo.insertSyncCharacter(character.character_id, character.book_id, userId, encryptedCharacterFirstName, encryptedCharacterLastName, encryptedCharacterCategory, encryptedCharacterTitle, encryptedCharacterImage, encryptedCharacterRole, encryptedCharacterBiography, encryptedCharacterHistory, character.last_update, lang); + const characterData = { + firstName: System.encryptDataWithUserKey(character.first_name, userEncryptionKey), + lastName: character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null, + nickname: character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null, + age: character.age ? System.encryptDataWithUserKey(character.age, userEncryptionKey) : null, + gender: character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null, + species: character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null, + nationality: character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null, + status: character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null, + category: System.encryptDataWithUserKey(character.category, userEncryptionKey), + title: character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null, + image: character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null, + role: character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null, + biography: character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null, + history: character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null, + speechPattern: character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null, + catchphrase: character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null, + residence: character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null, + notes: character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null, + color: character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null + }; + return CharacterRepo.insertSyncCharacter(character.character_id, character.book_id, userId, characterData, character.last_update, lang); }); if (!charactersInserted) return false; diff --git a/electron/database/models/Sync.ts b/electron/database/models/Sync.ts index 3fd2f21..e002227 100644 --- a/electron/database/models/Sync.ts +++ b/electron/database/models/Sync.ts @@ -200,11 +200,22 @@ export default class Sync { ...characterRecord, first_name: System.decryptDataWithUserKey(characterRecord.first_name, userEncryptionKey), last_name: characterRecord.last_name ? System.decryptDataWithUserKey(characterRecord.last_name, userEncryptionKey) : null, + nickname: characterRecord.nickname ? System.decryptDataWithUserKey(characterRecord.nickname, userEncryptionKey) : null, + age: characterRecord.age ? System.decryptDataWithUserKey(characterRecord.age, userEncryptionKey) : null, + gender: characterRecord.gender ? System.decryptDataWithUserKey(characterRecord.gender, userEncryptionKey) : null, + species: characterRecord.species ? System.decryptDataWithUserKey(characterRecord.species, userEncryptionKey) : null, + nationality: characterRecord.nationality ? System.decryptDataWithUserKey(characterRecord.nationality, userEncryptionKey) : null, + status: characterRecord.status ? System.decryptDataWithUserKey(characterRecord.status, userEncryptionKey) : null, category: System.decryptDataWithUserKey(characterRecord.category, userEncryptionKey), title: characterRecord.title ? System.decryptDataWithUserKey(characterRecord.title, userEncryptionKey) : null, role: characterRecord.role ? System.decryptDataWithUserKey(characterRecord.role, userEncryptionKey) : null, biography: characterRecord.biography ? System.decryptDataWithUserKey(characterRecord.biography, userEncryptionKey) : null, - history: characterRecord.history ? System.decryptDataWithUserKey(characterRecord.history, userEncryptionKey) : null + history: characterRecord.history ? System.decryptDataWithUserKey(characterRecord.history, userEncryptionKey) : null, + speech_pattern: characterRecord.speech_pattern ? System.decryptDataWithUserKey(characterRecord.speech_pattern, userEncryptionKey) : null, + catchphrase: characterRecord.catchphrase ? System.decryptDataWithUserKey(characterRecord.catchphrase, userEncryptionKey) : null, + residence: characterRecord.residence ? System.decryptDataWithUserKey(characterRecord.residence, userEncryptionKey) : null, + notes: characterRecord.notes ? System.decryptDataWithUserKey(characterRecord.notes, userEncryptionKey) : null, + color: characterRecord.color ? System.decryptDataWithUserKey(characterRecord.color, userEncryptionKey) : null }); } } @@ -562,21 +573,34 @@ export default class Sync { if (serverCharacters && serverCharacters.length > 0) { for (const serverCharacter of serverCharacters) { const characterExists: boolean = CharacterRepo.isCharacterExist(userId, serverCharacter.character_id, lang); - const encryptedFirstName: string = System.encryptDataWithUserKey(serverCharacter.first_name, userEncryptionKey); - const encryptedLastName: string = System.encryptDataWithUserKey(serverCharacter.last_name ? serverCharacter.last_name : '', userEncryptionKey); - const encryptedCategory: string = System.encryptDataWithUserKey(serverCharacter.category, userEncryptionKey); - const encryptedTitle: string = System.encryptDataWithUserKey(serverCharacter.title ? serverCharacter.title : '', userEncryptionKey); - const encryptedRole: string = System.encryptDataWithUserKey(serverCharacter.role ? serverCharacter.role : '', userEncryptionKey); - const encryptedImage: string = System.encryptDataWithUserKey(serverCharacter.image ? serverCharacter.image : '', userEncryptionKey); - const encryptedBiography: string = System.encryptDataWithUserKey(serverCharacter.biography ? serverCharacter.biography : '', userEncryptionKey); - const encryptedHistory: string = System.encryptDataWithUserKey(serverCharacter.history ? serverCharacter.history : '', userEncryptionKey); + const characterData = { + firstName: System.encryptDataWithUserKey(serverCharacter.first_name, userEncryptionKey), + lastName: System.encryptDataWithUserKey(serverCharacter.last_name ? serverCharacter.last_name : '', userEncryptionKey), + nickname: System.encryptDataWithUserKey(serverCharacter.nickname ? serverCharacter.nickname : '', userEncryptionKey), + age: System.encryptDataWithUserKey(serverCharacter.age ? serverCharacter.age : '', userEncryptionKey), + gender: System.encryptDataWithUserKey(serverCharacter.gender ? serverCharacter.gender : '', userEncryptionKey), + species: System.encryptDataWithUserKey(serverCharacter.species ? serverCharacter.species : '', userEncryptionKey), + nationality: System.encryptDataWithUserKey(serverCharacter.nationality ? serverCharacter.nationality : '', userEncryptionKey), + status: System.encryptDataWithUserKey(serverCharacter.status ? serverCharacter.status : 'alive', userEncryptionKey), + category: System.encryptDataWithUserKey(serverCharacter.category, userEncryptionKey), + title: System.encryptDataWithUserKey(serverCharacter.title ? serverCharacter.title : '', userEncryptionKey), + image: System.encryptDataWithUserKey(serverCharacter.image ? serverCharacter.image : '', userEncryptionKey), + role: System.encryptDataWithUserKey(serverCharacter.role ? serverCharacter.role : '', userEncryptionKey), + biography: System.encryptDataWithUserKey(serverCharacter.biography ? serverCharacter.biography : '', userEncryptionKey), + history: System.encryptDataWithUserKey(serverCharacter.history ? serverCharacter.history : '', userEncryptionKey), + speechPattern: System.encryptDataWithUserKey(serverCharacter.speech_pattern ? serverCharacter.speech_pattern : '', userEncryptionKey), + catchphrase: System.encryptDataWithUserKey(serverCharacter.catchphrase ? serverCharacter.catchphrase : '', userEncryptionKey), + residence: System.encryptDataWithUserKey(serverCharacter.residence ? serverCharacter.residence : '', userEncryptionKey), + notes: System.encryptDataWithUserKey(serverCharacter.notes ? serverCharacter.notes : '', userEncryptionKey), + color: System.encryptDataWithUserKey(serverCharacter.color ? serverCharacter.color : '', userEncryptionKey) + }; if (characterExists) { - const updateSuccessful: boolean = CharacterRepo.updateCharacter(userId, serverCharacter.character_id, encryptedFirstName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, serverCharacter.last_update); + const updateSuccessful: boolean = CharacterRepo.updateCharacter(userId, serverCharacter.character_id, characterData, serverCharacter.last_update, lang); if (!updateSuccessful) { return false; } } else { - const insertSuccessful: boolean = CharacterRepo.insertSyncCharacter(serverCharacter.character_id, bookId, userId, encryptedFirstName, encryptedLastName, encryptedCategory, encryptedTitle, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, serverCharacter.last_update, lang); + const insertSuccessful: boolean = CharacterRepo.insertSyncCharacter(serverCharacter.character_id, bookId, userId, characterData, serverCharacter.last_update, lang); if (!insertSuccessful) { return false; } diff --git a/electron/database/models/Upload.ts b/electron/database/models/Upload.ts index 5a885d8..05ae28e 100644 --- a/electron/database/models/Upload.ts +++ b/electron/database/models/Upload.ts @@ -168,11 +168,22 @@ export default class Upload { ...character, first_name: System.decryptDataWithUserKey(character.first_name, userEncryptionKey), last_name: character.last_name ? System.decryptDataWithUserKey(character.last_name, userEncryptionKey) : null, + nickname: character.nickname ? System.decryptDataWithUserKey(character.nickname, userEncryptionKey) : null, + age: character.age ? System.decryptDataWithUserKey(character.age, userEncryptionKey) : null, + gender: character.gender ? System.decryptDataWithUserKey(character.gender, userEncryptionKey) : null, + species: character.species ? System.decryptDataWithUserKey(character.species, userEncryptionKey) : null, + nationality: character.nationality ? System.decryptDataWithUserKey(character.nationality, userEncryptionKey) : null, + status: character.status ? System.decryptDataWithUserKey(character.status, userEncryptionKey) : null, category: System.decryptDataWithUserKey(character.category, userEncryptionKey), title: character.title ? System.decryptDataWithUserKey(character.title, userEncryptionKey) : null, role: character.role ? System.decryptDataWithUserKey(character.role, userEncryptionKey) : null, biography: character.biography ? System.decryptDataWithUserKey(character.biography, userEncryptionKey) : null, - history: character.history ? System.decryptDataWithUserKey(character.history, userEncryptionKey) : null + history: character.history ? System.decryptDataWithUserKey(character.history, userEncryptionKey) : null, + speech_pattern: character.speech_pattern ? System.decryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null, + catchphrase: character.catchphrase ? System.decryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null, + residence: character.residence ? System.decryptDataWithUserKey(character.residence, userEncryptionKey) : null, + notes: character.notes ? System.decryptDataWithUserKey(character.notes, userEncryptionKey) : null, + color: character.color ? System.decryptDataWithUserKey(character.color, userEncryptionKey) : null })); const characterAttributes: BookCharactersAttributesTable[] = encryptedCharacterAttributes.map((attribute: BookCharactersAttributesTable): BookCharactersAttributesTable => ({ diff --git a/electron/database/repositories/character.repository.ts b/electron/database/repositories/character.repository.ts index 7f83baf..7e4e5ba 100644 --- a/electron/database/repositories/character.repository.ts +++ b/electron/database/repositories/character.repository.ts @@ -7,12 +7,23 @@ export interface BookCharactersTable extends Record { user_id: string; first_name: string; last_name: string | null; + nickname: string | null; + age: string | null; + gender: string | null; + species: string | null; + nationality: string | null; + status: string | null; category: string; title: string | null; image: string | null; role: string | null; biography: string | null; history: string | null; + speech_pattern: string | null; + catchphrase: string | null; + residence: string | null; + notes: string | null; + color: string | null; last_update: number; } @@ -43,12 +54,23 @@ export interface CharacterResult extends Record { character_id: string; first_name: string; last_name: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: string; title: string; category: string; image: string; role: string; biography: string; history: string; + speech_pattern: string; + catchphrase: string; + residence: string; + notes: string; + color: string; } export interface AttributeResult extends Record { @@ -81,7 +103,7 @@ export default class CharacterRepo { public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] { try { const db: Database = System.getDb(); - const query: string = 'SELECT character_id, first_name, last_name, title, category, image, role, biography, history FROM book_characters WHERE book_id=? AND user_id=?'; + const query: string = 'SELECT character_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color FROM book_characters WHERE book_id=? AND user_id=?'; const params: SQLiteValue[] = [bookId, userId]; const characters: CharacterResult[] = db.all(query, params) as CharacterResult[]; return characters; @@ -100,23 +122,48 @@ export default class CharacterRepo { * Adds a new character to the database. * @param userId - The unique identifier of the user * @param characterId - The unique identifier for the new character - * @param encryptedName - The encrypted first name of the character - * @param encryptedLastName - The encrypted last name of the character - * @param encryptedTitle - The encrypted title of the character - * @param encryptedCategory - The encrypted category of the character - * @param encryptedImage - The encrypted image path of the character - * @param encryptedRole - The encrypted role of the character - * @param encryptedBiography - The encrypted biography of the character - * @param encryptedHistory - The encrypted history of the character + * @param characterData - Object containing all encrypted character fields * @param bookId - The unique identifier of the book * @param lang - The language for error messages ('fr' or 'en') * @returns The character ID if successful */ - public static addNewCharacter(userId: string, characterId: string, encryptedName: string, encryptedLastName: string, encryptedTitle: string, encryptedCategory: string, encryptedImage: string, encryptedRole: string, encryptedBiography: string, encryptedHistory: string, bookId: string, lang: 'fr' | 'en' = 'fr'): string { + public static addNewCharacter(userId: string, characterId: string, characterData: { + firstName: string; + lastName: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: string; + title: string; + category: string; + image: string; + role: string; + biography: string; + history: string; + speechPattern: string; + catchphrase: string; + residence: string; + notes: string; + color: string; + }, bookId: string, lang: 'fr' | 'en' = 'fr'): string { try { const db: Database = System.getDb(); - const query: string = 'INSERT INTO `book_characters` (character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'; - const params: SQLiteValue[] = [characterId, bookId, userId, encryptedName, encryptedLastName, encryptedCategory, encryptedTitle, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, System.timeStampInSeconds()]; + const query: string = `INSERT INTO book_characters ( + character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, + category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`; + const params: SQLiteValue[] = [ + characterId, bookId, userId, + characterData.firstName, characterData.lastName, characterData.nickname, + characterData.age, characterData.gender, characterData.species, + characterData.nationality, characterData.status, characterData.category, + characterData.title, characterData.image, characterData.role, + characterData.biography, characterData.history, characterData.speechPattern, + characterData.catchphrase, characterData.residence, characterData.notes, + characterData.color, System.timeStampInSeconds() + ]; const insertResult: RunResult = db.run(query, params); if (!insertResult || insertResult.changes === 0) { throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du personnage.` : `Error adding character.`); @@ -168,23 +215,48 @@ export default class CharacterRepo { * Updates an existing character's information. * @param userId - The unique identifier of the user * @param id - The unique identifier of the character to update - * @param encryptedName - The encrypted first name of the character - * @param encryptedLastName - The encrypted last name of the character - * @param encryptedTitle - The encrypted title of the character - * @param encryptedCategory - The encrypted category of the character - * @param encryptedImage - The encrypted image path of the character - * @param encryptedRole - The encrypted role of the character - * @param encryptedBiography - The encrypted biography of the character - * @param encryptedHistory - The encrypted history of the character + * @param characterData - Object containing all encrypted character fields * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful, false otherwise */ - static updateCharacter(userId: string, id: string, encryptedName: string, encryptedLastName: string, encryptedTitle: string, encryptedCategory: string, encryptedImage: string, encryptedRole: string, encryptedBiography: string, encryptedHistory: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { + static updateCharacter(userId: string, id: string, characterData: { + firstName: string; + lastName: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: string; + title: string; + category: string; + image: string; + role: string; + biography: string; + history: string; + speechPattern: string; + catchphrase: string; + residence: string; + notes: string; + color: string; + }, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); - const query: string = 'UPDATE `book_characters` SET `first_name`=?,`last_name`=?,`title`=?,`category`=?,`image`=?,`role`=?,`biography`=?,`history`=?,`last_update`=? WHERE `character_id`=? AND `user_id`=?'; - const params: SQLiteValue[] = [encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, lastUpdate, id, userId]; + const query: string = `UPDATE book_characters SET + first_name=?, last_name=?, nickname=?, age=?, gender=?, species=?, nationality=?, status=?, + title=?, category=?, image=?, role=?, biography=?, history=?, + speech_pattern=?, catchphrase=?, residence=?, notes=?, color=?, last_update=? + WHERE character_id=? AND user_id=?`; + const params: SQLiteValue[] = [ + characterData.firstName, characterData.lastName, characterData.nickname, + characterData.age, characterData.gender, characterData.species, + characterData.nationality, characterData.status, characterData.title, + characterData.category, characterData.image, characterData.role, + characterData.biography, characterData.history, characterData.speechPattern, + characterData.catchphrase, characterData.residence, characterData.notes, + characterData.color, lastUpdate, id, userId + ]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { @@ -396,7 +468,9 @@ export default class CharacterRepo { static async fetchBookCharacters(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); - const query: string = 'SELECT character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update FROM book_characters WHERE user_id=? AND book_id=?'; + const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, + category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update + FROM book_characters WHERE user_id=? AND book_id=?`; const params: SQLiteValue[] = [userId, bookId]; const characters: BookCharactersTable[] = db.all(query, params) as BookCharactersTable[]; return characters; @@ -487,24 +561,48 @@ export default class CharacterRepo { * @param characterId - The unique identifier of the character * @param bookId - The unique identifier of the book * @param userId - The unique identifier of the user - * @param firstName - The first name of the character - * @param lastName - The last name of the character (nullable) - * @param category - The category of the character - * @param title - The title of the character (nullable) - * @param image - The image path of the character (nullable) - * @param role - The role of the character (nullable) - * @param biography - The biography of the character (nullable) - * @param history - The history of the character (nullable) + * @param characterData - Object containing all character fields * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion was successful, false otherwise */ - static insertSyncCharacter(characterId: string, bookId: string, userId: string, firstName: string, lastName: string | null, category: string, title: string | null, image: string | null, role: string | null, biography: string | null, history: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { + static insertSyncCharacter(characterId: string, bookId: string, userId: string, characterData: { + firstName: string; + lastName: string | null; + nickname: string | null; + age: string | null; + gender: string | null; + species: string | null; + nationality: string | null; + status: string | null; + category: string; + title: string | null; + image: string | null; + role: string | null; + biography: string | null; + history: string | null; + speechPattern: string | null; + catchphrase: string | null; + residence: string | null; + notes: string | null; + color: string | null; + }, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); - const query: string = `INSERT INTO book_characters (character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; - const params: SQLiteValue[] = [characterId, bookId, userId, firstName, lastName, category, title, image, role, biography, history, lastUpdate]; + const query: string = `INSERT INTO book_characters ( + character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, + category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; + const params: SQLiteValue[] = [ + characterId, bookId, userId, + characterData.firstName, characterData.lastName, characterData.nickname, + characterData.age, characterData.gender, characterData.species, + characterData.nationality, characterData.status, characterData.category, + characterData.title, characterData.image, characterData.role, + characterData.biography, characterData.history, characterData.speechPattern, + characterData.catchphrase, characterData.residence, characterData.notes, + characterData.color, lastUpdate + ]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { @@ -555,7 +653,8 @@ export default class CharacterRepo { static async fetchCompleteCharacterById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); - const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update + const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, + category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM book_characters WHERE character_id = ?`; const params: SQLiteValue[] = [id]; diff --git a/electron/database/schema.ts b/electron/database/schema.ts index d673e4b..14efe07 100644 --- a/electron/database/schema.ts +++ b/electron/database/schema.ts @@ -13,13 +13,26 @@ type Database = sqlite3.Database; // MIGRATIONS // ============================================================================= -const schemaVersion = 1; +const schemaVersion = 2; /** * DEV ONLY - S'exécute à chaque refresh, pas besoin de version * Mets ta query, test, efface après */ -const devQueries: string[] = []; +const devQueries: string[] = [ + // Nouveaux champs de personnages + `ALTER TABLE book_characters ADD COLUMN nickname TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN age TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN gender TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN species TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN nationality TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN status TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN speech_pattern TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN catchphrase TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN residence TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN notes TEXT DEFAULT NULL`, + `ALTER TABLE book_characters ADD COLUMN color TEXT DEFAULT NULL`, +]; const isDev:boolean = !app.isPackaged; @@ -86,6 +99,19 @@ function migrateFromOldSystem(db: Database): void { // Add spells_enabled column to book_tools if missing addColumn(db, 'book_tools', 'spells_enabled', 'INTEGER NOT NULL DEFAULT 0'); + // Add new character fields if missing + addColumn(db, 'book_characters', 'nickname', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'age', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'gender', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'species', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'nationality', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'status', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'speech_pattern', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'catchphrase', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'residence', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'notes', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'color', 'TEXT DEFAULT NULL'); + // Create book_spell_tags table if missing db.exec(` CREATE TABLE IF NOT EXISTS book_spell_tags ( @@ -208,6 +234,21 @@ export function runMigrations(db: Database): void { db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`); } + // v2 - Add new character fields (nickname, age, gender, species, nationality, status, etc.) + if (currentVersion < 2) { + addColumn(db, 'book_characters', 'nickname', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'age', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'gender', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'species', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'nationality', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'status', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'speech_pattern', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'catchphrase', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'residence', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'notes', 'TEXT DEFAULT NULL'); + addColumn(db, 'book_characters', 'color', 'TEXT DEFAULT NULL'); + } + setDbVersion(db, schemaVersion); } @@ -346,12 +387,23 @@ export function initializeSchema(db: Database): void { user_id TEXT NOT NULL, first_name TEXT NOT NULL, last_name TEXT, + nickname TEXT, + age TEXT, + gender TEXT, + species TEXT, + nationality TEXT, + status TEXT, category TEXT NOT NULL, title TEXT, image TEXT, role TEXT, biography TEXT, history TEXT, + speech_pattern TEXT, + catchphrase TEXT, + residence TEXT, + notes TEXT, + color TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE ); diff --git a/lib/locales/en.json b/lib/locales/en.json index 81c27ad..a9de3eb 100644 --- a/lib/locales/en.json +++ b/lib/locales/en.json @@ -436,19 +436,47 @@ "back": "Back", "newCharacter": "New character", "basicInfo": "Basic information", - "name": "Name", - "namePlaceholder": "Enter a name", + "name": "First name", + "namePlaceholder": "Enter a first name", "lastName": "Last name", "lastNamePlaceholder": "Example: Smith", + "nickname": "Nickname", + "nicknamePlaceholder": "Nickname or alias", "role": "Role", "title": "Title", + "titlePlaceholder": "Example: King, Captain, Doctor...", + "gender": "Gender", + "genderPlaceholder": "Character's gender", + "age": "Age", + "agePlaceholder": "Character's age", "historySection": "Background", "biography": "Biography", "biographyPlaceholder": "Character biography.", "history": "History", "historyPlaceholder": "Character history...", - "roleFull": "Role", + "roleFull": "Role in the story", "roleFullPlaceholder": "Role of the character in the story", + "advancedMode": "Advanced mode", + "showAdvanced": "Show", + "hideAdvanced": "Hide", + "identitySection": "Extended identity", + "species": "Species", + "speciesPlaceholder": "Human, Elf, Vampire...", + "nationality": "Nationality", + "nationalityPlaceholder": "Country or region of origin", + "status": "Status", + "residence": "Residence", + "residencePlaceholder": "Current place of residence", + "voiceSection": "Character voice", + "speechPattern": "Speech pattern", + "speechPatternPlaceholder": "How does this character speak? Accent, speech quirks...", + "catchphrase": "Catchphrase", + "catchphrasePlaceholder": "A signature phrase of the character", + "authorSection": "Author notes", + "notes": "Notes", + "notesPlaceholder": "Personal notes about this character...", + "colorLabel": "Color", + "colorPlaceholder": "Color associated with the character", "fetchAttributesError": "Error fetching attributes.", "deleteTitle": "Delete character", "deleteMessage": "Are you sure you want to delete {name}? This action cannot be undone." diff --git a/lib/locales/fr.json b/lib/locales/fr.json index e1f65e1..a84c3e0 100644 --- a/lib/locales/fr.json +++ b/lib/locales/fr.json @@ -436,19 +436,47 @@ "back": "Retour", "newCharacter": "Nouveau personnage", "basicInfo": "Informations de base", - "name": "Nom", - "namePlaceholder": "Entrer un nom", + "name": "Prénom", + "namePlaceholder": "Entrer un prénom", "lastName": "Nom de famille", "lastNamePlaceholder": "Exemple : Smith", + "nickname": "Surnom", + "nicknamePlaceholder": "Surnom ou alias du personnage", "role": "Rôle", "title": "Titre", - "historySection": "Parcourt", + "titlePlaceholder": "Exemple : Roi, Capitaine, Docteur...", + "gender": "Genre", + "genderPlaceholder": "Genre du personnage", + "age": "Âge", + "agePlaceholder": "Âge du personnage", + "historySection": "Parcours", "biography": "Biographie", "biographyPlaceholder": "La biographie du personnage.", "history": "Histoire", "historyPlaceholder": "Histoire du personnage...", - "roleFull": "Rôle", + "roleFull": "Rôle dans l'histoire", "roleFullPlaceholder": "Rôle du personnage dans l'histoire", + "advancedMode": "Mode avancé", + "showAdvanced": "Afficher", + "hideAdvanced": "Masquer", + "identitySection": "Identité étendue", + "species": "Espèce", + "speciesPlaceholder": "Humain, Elfe, Vampire...", + "nationality": "Nationalité", + "nationalityPlaceholder": "Pays ou région d'origine", + "status": "Statut", + "residence": "Résidence", + "residencePlaceholder": "Lieu de résidence actuel", + "voiceSection": "Voix du personnage", + "speechPattern": "Pattern de parole", + "speechPatternPlaceholder": "Comment parle ce personnage ? Accent, tics de langage...", + "catchphrase": "Phrase fétiche", + "catchphrasePlaceholder": "Une phrase signature du personnage", + "authorSection": "Notes de l'auteur", + "notes": "Notes", + "notesPlaceholder": "Notes personnelles sur ce personnage...", + "colorLabel": "Couleur", + "colorPlaceholder": "Couleur associée au personnage", "fetchAttributesError": "Erreur lors de la récupération des attributs.", "deleteTitle": "Supprimer le personnage", "deleteMessage": "Êtes-vous sûr de vouloir supprimer {name} ? Cette action est irréversible." diff --git a/lib/models/Character.ts b/lib/models/Character.ts index 24625c8..dcb7a0f 100755 --- a/lib/models/Character.ts +++ b/lib/models/Character.ts @@ -7,6 +7,16 @@ import { faShieldAlt, faUsers, faWrench, + faRoute, + faUserSecret, + faGhost, + faHeartBroken, + faHandHoldingHeart, + faBolt, + faQuoteLeft, + faFingerprint, + faBox, + faPeopleGroup, } from '@fortawesome/free-solid-svg-icons'; import {SelectBoxProps} from "@/shared/interface"; @@ -31,100 +41,10 @@ export const characterCategories: SelectBoxProps[] = [ }, ]; -export const characterTitle: SelectBoxProps[] = [ - {value: 'none', label: 'Aucun'}, - {value: 'king', label: 'Roi'}, - {value: 'queen', label: 'Reine'}, - {value: 'emperor', label: 'Empereur'}, - {value: 'empress', label: 'Impératrice'}, - {value: 'prince', label: 'Prince'}, - {value: 'princess', label: 'Princesse'}, - {value: 'duke', label: 'Duc'}, - {value: 'duchess', label: 'Duchesse'}, - {value: 'count', label: 'Comte'}, - {value: 'countess', label: 'Comtesse'}, - {value: 'baron', label: 'Baron'}, - {value: 'baroness', label: 'Baronne'}, - {value: 'lord', label: 'Seigneur'}, - {value: 'lady', label: 'Dame'}, - {value: 'knight', label: 'Chevalier'}, - {value: 'squire', label: 'Écuyer'}, - {value: 'warrior', label: 'Guerrier'}, - {value: 'general', label: 'Général'}, - {value: 'commander', label: 'Commandant'}, - {value: 'captain', label: 'Capitaine'}, - {value: 'soldier', label: 'Soldat'}, - {value: 'mercenary', label: 'Mercenaire'}, - {value: 'assassin', label: 'Assassin'}, - {value: 'thief', label: 'Voleur'}, - {value: 'spy', label: 'Espion'}, - {value: 'archmage', label: 'Archimage'}, - {value: 'sorcerer', label: 'Sorcier'}, - {value: 'witch', label: 'Sorcière'}, - {value: 'warlock', label: 'Mage Noir'}, - {value: 'druid', label: 'Druide'}, - {value: 'priest', label: 'Prêtre'}, - {value: 'prophet', label: 'Prophète'}, - {value: 'oracle', label: 'Oracle'}, - {value: 'seer', label: 'Voyant'}, - {value: 'scholar', label: 'Érudit'}, - {value: 'alchemist', label: 'Alchimiste'}, - {value: 'healer', label: 'Guérisseur'}, - {value: 'bard', label: 'Barde'}, - {value: 'hermit', label: 'Ermite'}, - {value: 'noble', label: 'Noble'}, - {value: 'peasant', label: 'Paysan'}, - {value: 'merchant', label: 'Marchand'}, - {value: 'sailor', label: 'Marin'}, - {value: 'pirate', label: 'Pirate'}, - {value: 'slave', label: 'Esclave'}, - {value: 'gladiator', label: 'Gladiateur'}, - {value: 'champion', label: 'Champion'}, - {value: 'outlaw', label: 'Hors-la-loi'}, - {value: 'hunter', label: 'Chasseur'}, - {value: 'beastmaster', label: 'Maître des Bêtes'}, - {value: 'ranger', label: 'Rôdeur'}, - {value: 'warden', label: 'Gardien'}, - {value: 'sentinel', label: 'Sentinelle'}, - {value: 'herald', label: 'Héraut'}, - {value: 'messenger', label: 'Messager'}, - {value: 'pilgrim', label: 'Pèlerin'}, - {value: 'nomad', label: 'Nomade'}, - {value: 'chieftain', label: 'Chef de Clan'}, - {value: 'high-priest', label: 'Grand Prêtre'}, - {value: 'inquisitor', label: 'Inquisiteur'}, - {value: 'judge', label: 'Juge'}, - {value: 'executioner', label: 'Bourreau'}, - {value: 'warden', label: 'Gardien de Prison'}, - {value: 'monk', label: 'Moine'}, - {value: 'abbot', label: 'Abbé'}, - {value: 'nun', label: 'Nonne'}, - {value: 'diplomat', label: 'Diplomate'}, - {value: 'ambassador', label: 'Ambassadeur'}, - {value: 'scientist', label: 'Scientifique'}, - {value: 'engineer', label: 'Ingénieur'}, - {value: 'inventor', label: 'Inventeur'}, - {value: 'architect', label: 'Architecte'}, - {value: 'scribe', label: 'Scribe'}, - {value: 'chronicler', label: 'Chroniqueur'}, - {value: 'storyteller', label: 'Conteur'}, - {value: 'actor', label: 'Acteur'}, - {value: 'musician', label: 'Musicien'}, - {value: 'artist', label: 'Artiste'}, - {value: 'sculptor', label: 'Sculpteur'}, - {value: 'orator', label: 'Orateur'}, - {value: 'revolutionary', label: 'Révolutionnaire'}, - {value: 'resistance-fighter', label: 'Résistant'}, - {value: 'freedom-fighter', label: 'Combattant de la Liberté'}, - {value: 'cult-leader', label: 'Chef de Secte'}, - {value: 'warlock-lord', label: 'Seigneur Noir'}, - {value: 'dark-prophet', label: 'Prophète du Chaos'}, - {value: 'warlord', label: 'Seigneur de Guerre'}, - {value: 'grandmaster', label: 'Grand Maître'}, - {value: 'tactician', label: 'Tacticien'}, - {value: 'archduke', label: 'Archiduc'}, - {value: 'high-king', label: 'Haut Roi'}, - {value: 'divine-champion', label: 'Champion Divin'}, +export const characterStatus: SelectBoxProps[] = [ + {value: 'alive', label: 'Vivant'}, + {value: 'dead', label: 'Décédé'}, + {value: 'unknown', label: 'Inconnu'}, ]; export interface Relation { @@ -147,6 +67,12 @@ export interface CharacterProps { id: string | null; name: string; lastName: string; + nickname: string; + age: string; + gender: string; + species: string; + nationality: string; + status: 'alive' | 'dead' | 'unknown'; category: CharacterCategory; title: string; image: string; @@ -158,9 +84,24 @@ export interface CharacterProps { strengths: Attribute[]; goals: Attribute[]; motivations: Attribute[]; + arc: Attribute[]; + secrets: Attribute[]; + fears: Attribute[]; + flaws: Attribute[]; + beliefs: Attribute[]; + conflicts: Attribute[]; + quotes: Attribute[]; + distinguishingMarks: Attribute[]; + items: Attribute[]; + affiliations: Attribute[]; role: string; biography?: string; history?: string; + speechPattern?: string; + catchphrase?: string; + residence?: string; + notes?: string; + color?: string; } export interface CharacterListResponse { @@ -175,7 +116,8 @@ export interface CharacterElement { icon: any; // Replace `any` with an appropriate type if you have a specific icon type. } -export const characterElementCategory: CharacterElement[] = [ +// Attributs de base (toujours visibles) +export const basicCharacterElements: CharacterElement[] = [ { title: 'Descriptions physiques', section: 'physical', @@ -188,6 +130,58 @@ export const characterElementCategory: CharacterElement[] = [ placeholder: 'Nouvelle Description Psychologique', icon: faBrain, }, +]; + +// Attributs avancés (visibles en mode avancé) +export const advancedCharacterElements: CharacterElement[] = [ + { + title: 'Signes distinctifs', + section: 'distinguishingMarks', + placeholder: 'Nouveau signe distinctif', + icon: faFingerprint, + }, + { + title: 'Arc du personnage', + section: 'arc', + placeholder: 'Nouvelle étape de l\'arc', + icon: faRoute, + }, + { + title: 'Secrets', + section: 'secrets', + placeholder: 'Nouveau secret', + icon: faUserSecret, + }, + { + title: 'Peurs', + section: 'fears', + placeholder: 'Nouvelle peur', + icon: faGhost, + }, + { + title: 'Défauts', + section: 'flaws', + placeholder: 'Nouveau défaut', + icon: faHeartBroken, + }, + { + title: 'Croyances', + section: 'beliefs', + placeholder: 'Nouvelle croyance', + icon: faHandHoldingHeart, + }, + { + title: 'Conflits internes', + section: 'conflicts', + placeholder: 'Nouveau conflit', + icon: faBolt, + }, + { + title: 'Citations', + section: 'quotes', + placeholder: 'Nouvelle citation', + icon: faQuoteLeft, + }, { title: 'Relations', section: 'relations', @@ -224,4 +218,22 @@ export const characterElementCategory: CharacterElement[] = [ placeholder: 'Nouvelle Motivation', icon: faFire, }, + { + title: 'Objets importants', + section: 'items', + placeholder: 'Nouvel objet', + icon: faBox, + }, + { + title: 'Affiliations', + section: 'affiliations', + placeholder: 'Nouvelle affiliation', + icon: faPeopleGroup, + }, +]; + +// Pour rétro-compatibilité, on garde characterElementCategory qui combine les deux +export const characterElementCategory: CharacterElement[] = [ + ...basicCharacterElements, + ...advancedCharacterElements, ];