Expand character model with additional attributes and advanced customization options

- Added fields such as `nickname`, `age`, `gender`, `species`, `nationality`, `status`, and others to enhance character customization.
- Modified localization files to include new field labels and placeholders.
- Updated `CharacterComponent` and `CharacterDetail` components with UI elements for the newly added attributes.
- Introduced "Advanced Mode" toggle to manage visibility of extended customization options.
- Refactored database models and repository methods (`addNewCharacter`, `updateCharacter`, and `fetchCharacters`) to handle the extended schema.
- Improved data encryption and decryption workflows for secure storage of added attributes.
- Enhanced user experience by reorganizing character customization layouts.
This commit is contained in:
natreex
2026-01-23 20:49:57 -05:00
parent 57bf0c6ec3
commit 0fbd3743e7
11 changed files with 806 additions and 211 deletions

View File

@@ -35,12 +35,23 @@ const initialCharacterState: CharacterProps = {
id: null, id: null,
name: '', name: '',
lastName: '', lastName: '',
nickname: '',
age: '',
gender: '',
species: '',
nationality: '',
status: 'alive',
category: 'none', category: 'none',
title: '', title: '',
role: '', role: '',
image: 'https://via.placeholder.com/150', image: 'https://via.placeholder.com/150',
biography: '', biography: '',
history: '', history: '',
speechPattern: '',
catchphrase: '',
residence: '',
notes: '',
color: '',
physical: [], physical: [],
psychological: [], psychological: [],
relations: [], relations: [],
@@ -49,6 +60,16 @@ const initialCharacterState: CharacterProps = {
strengths: [], strengths: [],
goals: [], goals: [],
motivations: [], motivations: [],
arc: [],
secrets: [],
fears: [],
flaws: [],
beliefs: [],
conflicts: [],
quotes: [],
distinguishingMarks: [],
items: [],
affiliations: [],
}; };
export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, ref: any) { export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, ref: any) {
@@ -102,7 +123,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
setBook({...book, tools: { setBook({...book, tools: {
characters: enabled, characters: enabled,
worlds: book.tools?.worlds ?? false, worlds: book.tools?.worlds ?? false,
locations: book.tools?.locations ?? false locations: book.tools?.locations ?? false,
spells: book.tools?.spells ?? false
}}); }});
} }
} catch (e: unknown) { } catch (e: unknown) {
@@ -133,7 +155,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
setBook({...book, tools: { setBook({...book, tools: {
characters: response.enabled, characters: response.enabled,
worlds: book.tools?.worlds ?? false, worlds: book.tools?.worlds ?? false,
locations: book.tools?.locations ?? false locations: book.tools?.locations ?? false,
spells: book.tools?.spells ?? false
}}); }});
} }
} }

View File

@@ -10,23 +10,26 @@ import {
CharacterAttribute, CharacterAttribute,
characterCategories, characterCategories,
CharacterElement, CharacterElement,
characterElementCategory, basicCharacterElements,
advancedCharacterElements,
CharacterProps, CharacterProps,
characterTitle characterStatus
} from "@/lib/models/Character"; } from "@/lib/models/Character";
import System from "@/lib/models/System"; import System from "@/lib/models/System";
import { import {
faAddressCard,
faArrowLeft, faArrowLeft,
faBook, faBook,
faLayerGroup,
faPlus, faPlus,
faSave, faSave,
faScroll, faScroll,
faUser faUser,
faSliders,
faGlobe,
faCommentDots,
faStickyNote
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 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 CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement";
import DeleteButton from "@/components/form/DeleteButton"; import DeleteButton from "@/components/form/DeleteButton";
import {useTranslations} from "next-intl"; import {useTranslations} from "next-intl";
@@ -67,6 +70,7 @@ export default function CharacterDetail(
const {book} = useContext(BookContext); const {book} = useContext(BookContext);
const {session} = useContext(SessionContext); const {session} = useContext(SessionContext);
const {errorMessage} = useContext(AlertContext); const {errorMessage} = useContext(AlertContext);
const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
useEffect((): void => { useEffect((): void => {
if (selectedCharacter?.id !== null) { if (selectedCharacter?.id !== null) {
@@ -104,13 +108,24 @@ export default function CharacterDetail(
setSelectedCharacter({ setSelectedCharacter({
id: selectedCharacter?.id ?? '', id: selectedCharacter?.id ?? '',
name: selectedCharacter?.name ?? '', name: selectedCharacter?.name ?? '',
image: selectedCharacter?.image ?? '',
lastName: selectedCharacter?.lastName ?? '', 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', category: selectedCharacter?.category ?? 'none',
title: selectedCharacter?.title ?? '', title: selectedCharacter?.title ?? '',
image: selectedCharacter?.image ?? '',
role: selectedCharacter?.role ?? '',
biography: selectedCharacter?.biography, biography: selectedCharacter?.biography,
history: selectedCharacter?.history, history: selectedCharacter?.history,
role: selectedCharacter?.role ?? '', speechPattern: selectedCharacter?.speechPattern,
catchphrase: selectedCharacter?.catchphrase,
residence: selectedCharacter?.residence,
notes: selectedCharacter?.notes,
color: selectedCharacter?.color,
physical: attributes.physical ?? [], physical: attributes.physical ?? [],
psychological: attributes.psychological ?? [], psychological: attributes.psychological ?? [],
relations: attributes.relations ?? [], relations: attributes.relations ?? [],
@@ -119,6 +134,16 @@ export default function CharacterDetail(
strengths: attributes.strengths ?? [], strengths: attributes.strengths ?? [],
goals: attributes.goals ?? [], goals: attributes.goals ?? [],
motivations: attributes.motivations ?? [], 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) { } catch (e: unknown) {
@@ -185,6 +210,17 @@ export default function CharacterDetail(
} }
/> />
<InputField
fieldName={t("characterDetail.nickname")}
input={
<TextInput
value={selectedCharacter?.nickname || ''}
setValue={(e) => handleCharacterChange('nickname', e.target.value)}
placeholder={t("characterDetail.nicknamePlaceholder")}
/>
}
/>
<InputField <InputField
fieldName={t("characterDetail.role")} fieldName={t("characterDetail.role")}
input={ input={
@@ -196,19 +232,39 @@ export default function CharacterDetail(
data={characterCategories} data={characterCategories}
/> />
} }
icon={faLayerGroup}
/> />
<InputField <InputField
fieldName={t("characterDetail.title")} fieldName={t("characterDetail.title")}
input={ input={
<SelectBox <TextInput
defaultValue={selectedCharacter?.title || 'none'} value={selectedCharacter?.title || ''}
onChangeCallBack={(e) => handleCharacterChange('title', e.target.value)} setValue={(e) => handleCharacterChange('title', e.target.value)}
data={characterTitle} placeholder={t("characterDetail.titlePlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.gender")}
input={
<TextInput
value={selectedCharacter?.gender || ''}
setValue={(e) => handleCharacterChange('gender', e.target.value)}
placeholder={t("characterDetail.genderPlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.age")}
input={
<TextInput
value={selectedCharacter?.age || ''}
setValue={(e) => handleCharacterChange('age', e.target.value)}
placeholder={t("characterDetail.agePlaceholder")}
/> />
} }
icon={faAddressCard}
/> />
</div> </div>
</CollapsableArea> </CollapsableArea>
@@ -253,9 +309,10 @@ export default function CharacterDetail(
</div> </div>
</CollapsableArea> </CollapsableArea>
{characterElementCategory.map((item: CharacterElement, index: number) => ( {/* Attributs de base - toujours visibles */}
{basicCharacterElements.map((item: CharacterElement, index: number) => (
<CharacterSectionElement <CharacterSectionElement
key={index} key={`basic-${index}`}
title={item.title} title={item.title}
section={item.section} section={item.section}
placeholder={item.placeholder} placeholder={item.placeholder}
@@ -266,6 +323,149 @@ export default function CharacterDetail(
handleRemoveElement={handleRemoveElement} handleRemoveElement={handleRemoveElement}
/> />
))} ))}
{/* Toggle Mode Avancé */}
<div className="flex items-center justify-between p-4 bg-secondary/30 rounded-xl border border-secondary/50">
<div className="flex items-center gap-3">
<FontAwesomeIcon icon={faSliders} className="text-primary w-5 h-5"/>
<span className="text-text-primary font-medium">{t("characterDetail.advancedMode")}</span>
</div>
<button
onClick={() => setShowAdvanced(!showAdvanced)}
className={`px-4 py-2 rounded-lg transition-all duration-200 ${
showAdvanced
? 'bg-primary text-white'
: 'bg-secondary/50 text-text-primary hover:bg-secondary'
}`}
>
{showAdvanced ? t("characterDetail.hideAdvanced") : t("characterDetail.showAdvanced")}
</button>
</div>
{/* Sections avancées - visibles uniquement si showAdvanced est true */}
{showAdvanced && (
<>
{/* Identité étendue */}
<CollapsableArea title={t("characterDetail.identitySection")} icon={faGlobe}>
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
<InputField
fieldName={t("characterDetail.species")}
input={
<TextInput
value={selectedCharacter?.species || ''}
setValue={(e) => handleCharacterChange('species', e.target.value)}
placeholder={t("characterDetail.speciesPlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.nationality")}
input={
<TextInput
value={selectedCharacter?.nationality || ''}
setValue={(e) => handleCharacterChange('nationality', e.target.value)}
placeholder={t("characterDetail.nationalityPlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.status")}
input={
<SelectBox
defaultValue={selectedCharacter?.status || 'alive'}
onChangeCallBack={(e) => setSelectedCharacter(prev =>
prev ? {...prev, status: e.target.value as CharacterProps['status']} : prev
)}
data={characterStatus}
/>
}
/>
<InputField
fieldName={t("characterDetail.residence")}
input={
<TextInput
value={selectedCharacter?.residence || ''}
setValue={(e) => handleCharacterChange('residence', e.target.value)}
placeholder={t("characterDetail.residencePlaceholder")}
/>
}
/>
</div>
</CollapsableArea>
{/* Voix du personnage */}
<CollapsableArea title={t("characterDetail.voiceSection")} icon={faCommentDots}>
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
<InputField
fieldName={t("characterDetail.speechPattern")}
input={
<TexteAreaInput
value={selectedCharacter?.speechPattern || ''}
setValue={(e) => handleCharacterChange('speechPattern', e.target.value)}
placeholder={t("characterDetail.speechPatternPlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.catchphrase")}
input={
<TextInput
value={selectedCharacter?.catchphrase || ''}
setValue={(e) => handleCharacterChange('catchphrase', e.target.value)}
placeholder={t("characterDetail.catchphrasePlaceholder")}
/>
}
/>
</div>
</CollapsableArea>
{/* Notes de l'auteur */}
<CollapsableArea title={t("characterDetail.authorSection")} icon={faStickyNote}>
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
<InputField
fieldName={t("characterDetail.notes")}
input={
<TexteAreaInput
value={selectedCharacter?.notes || ''}
setValue={(e) => handleCharacterChange('notes', e.target.value)}
placeholder={t("characterDetail.notesPlaceholder")}
/>
}
/>
<InputField
fieldName={t("characterDetail.colorLabel")}
input={
<TextInput
value={selectedCharacter?.color || ''}
setValue={(e) => handleCharacterChange('color', e.target.value)}
placeholder={t("characterDetail.colorPlaceholder")}
/>
}
/>
</div>
</CollapsableArea>
{/* Attributs avancés */}
{advancedCharacterElements.map((item: CharacterElement, index: number) => (
<CharacterSectionElement
key={`advanced-${index}`}
title={item.title}
section={item.section}
placeholder={item.placeholder}
icon={item.icon}
selectedCharacter={selectedCharacter as CharacterProps}
setSelectedCharacter={setSelectedCharacter}
handleAddElement={handleAddElement}
handleRemoveElement={handleRemoveElement}
/>
))}
</>
)}
</div> </div>
</div> </div>
); );

View File

@@ -13,6 +13,12 @@ export interface CharacterPropsPost {
id: string | null; id: string | null;
name: string; name: string;
lastName: string; lastName: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: 'alive' | 'dead' | 'unknown';
category: CharacterCategory; category: CharacterCategory;
title: string; title: string;
image: string; image: string;
@@ -24,9 +30,24 @@ export interface CharacterPropsPost {
strengths: { name: string }[]; strengths: { name: string }[];
goals: { name: string }[]; goals: { name: string }[];
motivations: { 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; role: string;
biography?: string; biography?: string;
history?: string; history?: string;
speechPattern?: string;
catchphrase?: string;
residence?: string;
notes?: string;
color?: string;
} }
@@ -34,12 +55,23 @@ export interface CharacterProps {
id: string; id: string;
name: string; name: string;
lastName: string; lastName: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: string;
title: string; title: string;
category: string; category: string;
image: string; image: string;
role: string; role: string;
biography: string; biography: string;
history: string; history: string;
speechPattern: string;
catchphrase: string;
residence: string;
notes: string;
color: string;
} }
export interface CharacterListResponse { export interface CharacterListResponse {
@@ -51,12 +83,23 @@ export interface CompleteCharacterProps {
id?: string; id?: string;
name: string; name: string;
lastName: string; lastName: string;
nickname?: string;
age?: string;
gender?: string;
species?: string;
nationality?: string;
status?: string;
title: string; title: string;
category: string; category: string;
image?: string; image?: string;
role: string; role: string;
biography: string; biography: string;
history: string; history: string;
speechPattern?: string;
catchphrase?: string;
residence?: string;
notes?: string;
color?: string;
[key: string]: Attribute[] | string | undefined; [key: string]: Attribute[] | string | undefined;
} }
@@ -108,12 +151,23 @@ export default class Character {
id: encryptedCharacter.character_id, id: encryptedCharacter.character_id,
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '', name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_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) : '', title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title, userEncryptionKey) : '',
category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '', category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '',
image: encryptedCharacter.image ? System.decryptDataWithUserKey(encryptedCharacter.image, userEncryptionKey) : '', image: encryptedCharacter.image ? System.decryptDataWithUserKey(encryptedCharacter.image, userEncryptionKey) : '',
role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '', role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '',
biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '', biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '',
history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history, 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 }; 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 { public static addNewCharacter(userId: string, character: CharacterPropsPost, bookId: string, lang: 'fr' | 'en' = 'fr', existingCharacterId?: string): string {
const userEncryptionKey: string = getUserEncryptionKey(userId); const userEncryptionKey: string = getUserEncryptionKey(userId);
const characterId: string = existingCharacterId || System.createUniqueId(); const characterId: string = existingCharacterId || System.createUniqueId();
const encryptedName: string = System.encryptDataWithUserKey(character.name, userEncryptionKey);
const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userEncryptionKey); const characterData = {
const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userEncryptionKey); firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
const encryptedImage: string = System.encryptDataWithUserKey(character.image, userEncryptionKey); nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
const encryptedRole: string = System.encryptDataWithUserKey(character.role, userEncryptionKey); age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey),
const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userEncryptionKey); gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userEncryptionKey); species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
CharacterRepo.addNewCharacter(userId, characterId, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, bookId, lang); 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); const characterPropertyKeys: string[] = Object.keys(character);
for (const propertyKey of characterPropertyKeys) { for (const propertyKey of characterPropertyKeys) {
if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) { if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) {
@@ -170,15 +239,30 @@ export default class Character {
if (!character.id) { if (!character.id) {
return false; return false;
} }
const encryptedName: string = System.encryptDataWithUserKey(character.name, userEncryptionKey);
const encryptedLastName: string = System.encryptDataWithUserKey(character.lastName, userEncryptionKey); const characterData = {
const encryptedTitle: string = System.encryptDataWithUserKey(character.title, userEncryptionKey); firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
const encryptedImage: string = System.encryptDataWithUserKey(character.image, userEncryptionKey); nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
const encryptedRole: string = System.encryptDataWithUserKey(character.role, userEncryptionKey); age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey),
const encryptedBiography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userEncryptionKey); gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
const encryptedHistory: string = System.encryptDataWithUserKey(character.history ? character.history : '', userEncryptionKey); species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
return CharacterRepo.updateCharacter(userId, character.id, encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, System.timeStampInSeconds(), lang); 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: '', id: '',
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '', name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '', lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title, userEncryptionKey) : '', nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname as string, userEncryptionKey) : '',
category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '', age: encryptedCharacter.age ? System.decryptDataWithUserKey(encryptedCharacter.age as string, userEncryptionKey) : '',
role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '', gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender as string, userEncryptionKey) : '',
biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '', species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species as string, userEncryptionKey) : '',
history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history, 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: [], physical: [],
psychological: [], psychological: [],
relations: [], relations: [],
@@ -297,7 +392,17 @@ export default class Character {
weaknesses: [], weaknesses: [],
strengths: [], strengths: [],
goals: [], goals: [],
motivations: [] motivations: [],
arc: [],
secrets: [],
fears: [],
flaws: [],
beliefs: [],
conflicts: [],
quotes: [],
distinguishingMarks: [],
items: [],
affiliations: []
}; };
completeCharactersMap.set(encryptedCharacter.character_id, decryptedCharacter); completeCharactersMap.set(encryptedCharacter.character_id, decryptedCharacter);
} }

View File

@@ -106,15 +106,28 @@ export default class Download {
if (!chapterInfosInserted) return false; if (!chapterInfosInserted) return false;
const charactersInserted: boolean = data.characters.every((character: BookCharactersTable): boolean => { const charactersInserted: boolean = data.characters.every((character: BookCharactersTable): boolean => {
const encryptedCharacterFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey); const characterData = {
const encryptedCharacterLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null; firstName: System.encryptDataWithUserKey(character.first_name, userEncryptionKey),
const encryptedCharacterCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); lastName: character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null,
const encryptedCharacterTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null; nickname: character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null,
const encryptedCharacterImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null; age: character.age ? System.encryptDataWithUserKey(character.age, userEncryptionKey) : null,
const encryptedCharacterRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null; gender: character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null,
const encryptedCharacterBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null; species: character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null,
const encryptedCharacterHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null; nationality: character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null,
return CharacterRepo.insertSyncCharacter(character.character_id, character.book_id, userId, encryptedCharacterFirstName, encryptedCharacterLastName, encryptedCharacterCategory, encryptedCharacterTitle, encryptedCharacterImage, encryptedCharacterRole, encryptedCharacterBiography, encryptedCharacterHistory, character.last_update, lang); 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; if (!charactersInserted) return false;

View File

@@ -200,11 +200,22 @@ export default class Sync {
...characterRecord, ...characterRecord,
first_name: System.decryptDataWithUserKey(characterRecord.first_name, userEncryptionKey), first_name: System.decryptDataWithUserKey(characterRecord.first_name, userEncryptionKey),
last_name: characterRecord.last_name ? System.decryptDataWithUserKey(characterRecord.last_name, userEncryptionKey) : null, 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), category: System.decryptDataWithUserKey(characterRecord.category, userEncryptionKey),
title: characterRecord.title ? System.decryptDataWithUserKey(characterRecord.title, userEncryptionKey) : null, title: characterRecord.title ? System.decryptDataWithUserKey(characterRecord.title, userEncryptionKey) : null,
role: characterRecord.role ? System.decryptDataWithUserKey(characterRecord.role, userEncryptionKey) : null, role: characterRecord.role ? System.decryptDataWithUserKey(characterRecord.role, userEncryptionKey) : null,
biography: characterRecord.biography ? System.decryptDataWithUserKey(characterRecord.biography, 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) { if (serverCharacters && serverCharacters.length > 0) {
for (const serverCharacter of serverCharacters) { for (const serverCharacter of serverCharacters) {
const characterExists: boolean = CharacterRepo.isCharacterExist(userId, serverCharacter.character_id, lang); const characterExists: boolean = CharacterRepo.isCharacterExist(userId, serverCharacter.character_id, lang);
const encryptedFirstName: string = System.encryptDataWithUserKey(serverCharacter.first_name, userEncryptionKey); const characterData = {
const encryptedLastName: string = System.encryptDataWithUserKey(serverCharacter.last_name ? serverCharacter.last_name : '', userEncryptionKey); firstName: System.encryptDataWithUserKey(serverCharacter.first_name, userEncryptionKey),
const encryptedCategory: string = System.encryptDataWithUserKey(serverCharacter.category, userEncryptionKey); lastName: System.encryptDataWithUserKey(serverCharacter.last_name ? serverCharacter.last_name : '', userEncryptionKey),
const encryptedTitle: string = System.encryptDataWithUserKey(serverCharacter.title ? serverCharacter.title : '', userEncryptionKey); nickname: System.encryptDataWithUserKey(serverCharacter.nickname ? serverCharacter.nickname : '', userEncryptionKey),
const encryptedRole: string = System.encryptDataWithUserKey(serverCharacter.role ? serverCharacter.role : '', userEncryptionKey); age: System.encryptDataWithUserKey(serverCharacter.age ? serverCharacter.age : '', userEncryptionKey),
const encryptedImage: string = System.encryptDataWithUserKey(serverCharacter.image ? serverCharacter.image : '', userEncryptionKey); gender: System.encryptDataWithUserKey(serverCharacter.gender ? serverCharacter.gender : '', userEncryptionKey),
const encryptedBiography: string = System.encryptDataWithUserKey(serverCharacter.biography ? serverCharacter.biography : '', userEncryptionKey); species: System.encryptDataWithUserKey(serverCharacter.species ? serverCharacter.species : '', userEncryptionKey),
const encryptedHistory: string = System.encryptDataWithUserKey(serverCharacter.history ? serverCharacter.history : '', 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) { 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) { if (!updateSuccessful) {
return false; return false;
} }
} else { } 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) { if (!insertSuccessful) {
return false; return false;
} }

View File

@@ -168,11 +168,22 @@ export default class Upload {
...character, ...character,
first_name: System.decryptDataWithUserKey(character.first_name, userEncryptionKey), first_name: System.decryptDataWithUserKey(character.first_name, userEncryptionKey),
last_name: character.last_name ? System.decryptDataWithUserKey(character.last_name, userEncryptionKey) : null, 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), category: System.decryptDataWithUserKey(character.category, userEncryptionKey),
title: character.title ? System.decryptDataWithUserKey(character.title, userEncryptionKey) : null, title: character.title ? System.decryptDataWithUserKey(character.title, userEncryptionKey) : null,
role: character.role ? System.decryptDataWithUserKey(character.role, userEncryptionKey) : null, role: character.role ? System.decryptDataWithUserKey(character.role, userEncryptionKey) : null,
biography: character.biography ? System.decryptDataWithUserKey(character.biography, 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 => ({ const characterAttributes: BookCharactersAttributesTable[] = encryptedCharacterAttributes.map((attribute: BookCharactersAttributesTable): BookCharactersAttributesTable => ({

View File

@@ -7,12 +7,23 @@ export interface BookCharactersTable extends Record<string, SQLiteValue> {
user_id: string; user_id: string;
first_name: string; first_name: string;
last_name: string | null; last_name: string | null;
nickname: string | null;
age: string | null;
gender: string | null;
species: string | null;
nationality: string | null;
status: string | null;
category: string; category: string;
title: string | null; title: string | null;
image: string | null; image: string | null;
role: string | null; role: string | null;
biography: string | null; biography: string | null;
history: string | null; history: string | null;
speech_pattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
last_update: number; last_update: number;
} }
@@ -43,12 +54,23 @@ export interface CharacterResult extends Record<string, SQLiteValue> {
character_id: string; character_id: string;
first_name: string; first_name: string;
last_name: string; last_name: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: string;
title: string; title: string;
category: string; category: string;
image: string; image: string;
role: string; role: string;
biography: string; biography: string;
history: string; history: string;
speech_pattern: string;
catchphrase: string;
residence: string;
notes: string;
color: string;
} }
export interface AttributeResult extends Record<string, SQLiteValue> { export interface AttributeResult extends Record<string, SQLiteValue> {
@@ -81,7 +103,7 @@ export default class CharacterRepo {
public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] { public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] {
try { try {
const db: Database = System.getDb(); 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 params: SQLiteValue[] = [bookId, userId];
const characters: CharacterResult[] = db.all(query, params) as CharacterResult[]; const characters: CharacterResult[] = db.all(query, params) as CharacterResult[];
return characters; return characters;
@@ -100,23 +122,48 @@ export default class CharacterRepo {
* Adds a new character to the database. * Adds a new character to the database.
* @param userId - The unique identifier of the user * @param userId - The unique identifier of the user
* @param characterId - The unique identifier for the new character * @param characterId - The unique identifier for the new character
* @param encryptedName - The encrypted first name of the character * @param characterData - Object containing all encrypted character fields
* @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 bookId - The unique identifier of the book * @param bookId - The unique identifier of the book
* @param lang - The language for error messages ('fr' or 'en') * @param lang - The language for error messages ('fr' or 'en')
* @returns The character ID if successful * @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 { try {
const db: Database = System.getDb(); 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 query: string = `INSERT INTO book_characters (
const params: SQLiteValue[] = [characterId, bookId, userId, encryptedName, encryptedLastName, encryptedCategory, encryptedTitle, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, System.timeStampInSeconds()]; 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); const insertResult: RunResult = db.run(query, params);
if (!insertResult || insertResult.changes === 0) { if (!insertResult || insertResult.changes === 0) {
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du personnage.` : `Error adding character.`); 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. * Updates an existing character's information.
* @param userId - The unique identifier of the user * @param userId - The unique identifier of the user
* @param id - The unique identifier of the character to update * @param id - The unique identifier of the character to update
* @param encryptedName - The encrypted first name of the character * @param characterData - Object containing all encrypted character fields
* @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 lastUpdate - The timestamp of the last update * @param lastUpdate - The timestamp of the last update
* @param lang - The language for error messages ('fr' or 'en') * @param lang - The language for error messages ('fr' or 'en')
* @returns True if the update was successful, false otherwise * @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 { try {
const db: Database = System.getDb(); 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 query: string = `UPDATE book_characters SET
const params: SQLiteValue[] = [encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, lastUpdate, id, userId]; 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); const updateResult: RunResult = db.run(query, params);
return updateResult.changes > 0; return updateResult.changes > 0;
} catch (error: unknown) { } catch (error: unknown) {
@@ -396,7 +468,9 @@ export default class CharacterRepo {
static async fetchBookCharacters(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<BookCharactersTable[]> { static async fetchBookCharacters(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<BookCharactersTable[]> {
try { try {
const db: Database = System.getDb(); 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 params: SQLiteValue[] = [userId, bookId];
const characters: BookCharactersTable[] = db.all(query, params) as BookCharactersTable[]; const characters: BookCharactersTable[] = db.all(query, params) as BookCharactersTable[];
return characters; return characters;
@@ -487,24 +561,48 @@ export default class CharacterRepo {
* @param characterId - The unique identifier of the character * @param characterId - The unique identifier of the character
* @param bookId - The unique identifier of the book * @param bookId - The unique identifier of the book
* @param userId - The unique identifier of the user * @param userId - The unique identifier of the user
* @param firstName - The first name of the character * @param characterData - Object containing all character fields
* @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 lastUpdate - The timestamp of the last update * @param lastUpdate - The timestamp of the last update
* @param lang - The language for error messages ('fr' or 'en') * @param lang - The language for error messages ('fr' or 'en')
* @returns True if the insertion was successful, false otherwise * @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 { try {
const db: Database = System.getDb(); 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) const query: string = `INSERT INTO book_characters (
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
const params: SQLiteValue[] = [characterId, bookId, userId, firstName, lastName, category, title, image, role, biography, history, lastUpdate]; 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); const insertResult: RunResult = db.run(query, params);
return insertResult.changes > 0; return insertResult.changes > 0;
} catch (error: unknown) { } catch (error: unknown) {
@@ -555,7 +653,8 @@ export default class CharacterRepo {
static async fetchCompleteCharacterById(id: string, lang: "fr" | "en"): Promise<BookCharactersTable[]> { static async fetchCompleteCharacterById(id: string, lang: "fr" | "en"): Promise<BookCharactersTable[]> {
try { try {
const db: Database = System.getDb(); 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 FROM book_characters
WHERE character_id = ?`; WHERE character_id = ?`;
const params: SQLiteValue[] = [id]; const params: SQLiteValue[] = [id];

View File

@@ -13,13 +13,26 @@ type Database = sqlite3.Database;
// MIGRATIONS // MIGRATIONS
// ============================================================================= // =============================================================================
const schemaVersion = 1; const schemaVersion = 2;
/** /**
* DEV ONLY - S'exécute à chaque refresh, pas besoin de version * DEV ONLY - S'exécute à chaque refresh, pas besoin de version
* Mets ta query, test, efface après * 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; const isDev:boolean = !app.isPackaged;
@@ -86,6 +99,19 @@ function migrateFromOldSystem(db: Database): void {
// Add spells_enabled column to book_tools if missing // Add spells_enabled column to book_tools if missing
addColumn(db, 'book_tools', 'spells_enabled', 'INTEGER NOT NULL DEFAULT 0'); 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 // Create book_spell_tags table if missing
db.exec(` db.exec(`
CREATE TABLE IF NOT EXISTS book_spell_tags ( 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)`); 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); setDbVersion(db, schemaVersion);
} }
@@ -346,12 +387,23 @@ export function initializeSchema(db: Database): void {
user_id TEXT NOT NULL, user_id TEXT NOT NULL,
first_name TEXT NOT NULL, first_name TEXT NOT NULL,
last_name TEXT, last_name TEXT,
nickname TEXT,
age TEXT,
gender TEXT,
species TEXT,
nationality TEXT,
status TEXT,
category TEXT NOT NULL, category TEXT NOT NULL,
title TEXT, title TEXT,
image TEXT, image TEXT,
role TEXT, role TEXT,
biography TEXT, biography TEXT,
history TEXT, history TEXT,
speech_pattern TEXT,
catchphrase TEXT,
residence TEXT,
notes TEXT,
color TEXT,
last_update INTEGER DEFAULT 0, last_update INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
); );

View File

@@ -436,19 +436,47 @@
"back": "Back", "back": "Back",
"newCharacter": "New character", "newCharacter": "New character",
"basicInfo": "Basic information", "basicInfo": "Basic information",
"name": "Name", "name": "First name",
"namePlaceholder": "Enter a name", "namePlaceholder": "Enter a first name",
"lastName": "Last name", "lastName": "Last name",
"lastNamePlaceholder": "Example: Smith", "lastNamePlaceholder": "Example: Smith",
"nickname": "Nickname",
"nicknamePlaceholder": "Nickname or alias",
"role": "Role", "role": "Role",
"title": "Title", "title": "Title",
"titlePlaceholder": "Example: King, Captain, Doctor...",
"gender": "Gender",
"genderPlaceholder": "Character's gender",
"age": "Age",
"agePlaceholder": "Character's age",
"historySection": "Background", "historySection": "Background",
"biography": "Biography", "biography": "Biography",
"biographyPlaceholder": "Character biography.", "biographyPlaceholder": "Character biography.",
"history": "History", "history": "History",
"historyPlaceholder": "Character history...", "historyPlaceholder": "Character history...",
"roleFull": "Role", "roleFull": "Role in the story",
"roleFullPlaceholder": "Role of the character 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.", "fetchAttributesError": "Error fetching attributes.",
"deleteTitle": "Delete character", "deleteTitle": "Delete character",
"deleteMessage": "Are you sure you want to delete {name}? This action cannot be undone." "deleteMessage": "Are you sure you want to delete {name}? This action cannot be undone."

View File

@@ -436,19 +436,47 @@
"back": "Retour", "back": "Retour",
"newCharacter": "Nouveau personnage", "newCharacter": "Nouveau personnage",
"basicInfo": "Informations de base", "basicInfo": "Informations de base",
"name": "Nom", "name": "Prénom",
"namePlaceholder": "Entrer un nom", "namePlaceholder": "Entrer un prénom",
"lastName": "Nom de famille", "lastName": "Nom de famille",
"lastNamePlaceholder": "Exemple : Smith", "lastNamePlaceholder": "Exemple : Smith",
"nickname": "Surnom",
"nicknamePlaceholder": "Surnom ou alias du personnage",
"role": "Rôle", "role": "Rôle",
"title": "Titre", "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", "biography": "Biographie",
"biographyPlaceholder": "La biographie du personnage.", "biographyPlaceholder": "La biographie du personnage.",
"history": "Histoire", "history": "Histoire",
"historyPlaceholder": "Histoire du personnage...", "historyPlaceholder": "Histoire du personnage...",
"roleFull": "Rôle", "roleFull": "Rôle dans l'histoire",
"roleFullPlaceholder": "Rôle du personnage 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.", "fetchAttributesError": "Erreur lors de la récupération des attributs.",
"deleteTitle": "Supprimer le personnage", "deleteTitle": "Supprimer le personnage",
"deleteMessage": "Êtes-vous sûr de vouloir supprimer {name} ? Cette action est irréversible." "deleteMessage": "Êtes-vous sûr de vouloir supprimer {name} ? Cette action est irréversible."

View File

@@ -7,6 +7,16 @@ import {
faShieldAlt, faShieldAlt,
faUsers, faUsers,
faWrench, faWrench,
faRoute,
faUserSecret,
faGhost,
faHeartBroken,
faHandHoldingHeart,
faBolt,
faQuoteLeft,
faFingerprint,
faBox,
faPeopleGroup,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import {SelectBoxProps} from "@/shared/interface"; import {SelectBoxProps} from "@/shared/interface";
@@ -31,100 +41,10 @@ export const characterCategories: SelectBoxProps[] = [
}, },
]; ];
export const characterTitle: SelectBoxProps[] = [ export const characterStatus: SelectBoxProps[] = [
{value: 'none', label: 'Aucun'}, {value: 'alive', label: 'Vivant'},
{value: 'king', label: 'Roi'}, {value: 'dead', label: 'Décédé'},
{value: 'queen', label: 'Reine'}, {value: 'unknown', label: 'Inconnu'},
{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 interface Relation { export interface Relation {
@@ -147,6 +67,12 @@ export interface CharacterProps {
id: string | null; id: string | null;
name: string; name: string;
lastName: string; lastName: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: 'alive' | 'dead' | 'unknown';
category: CharacterCategory; category: CharacterCategory;
title: string; title: string;
image: string; image: string;
@@ -158,9 +84,24 @@ export interface CharacterProps {
strengths: Attribute[]; strengths: Attribute[];
goals: Attribute[]; goals: Attribute[];
motivations: 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; role: string;
biography?: string; biography?: string;
history?: string; history?: string;
speechPattern?: string;
catchphrase?: string;
residence?: string;
notes?: string;
color?: string;
} }
export interface CharacterListResponse { 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. 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', title: 'Descriptions physiques',
section: 'physical', section: 'physical',
@@ -188,6 +130,58 @@ export const characterElementCategory: CharacterElement[] = [
placeholder: 'Nouvelle Description Psychologique', placeholder: 'Nouvelle Description Psychologique',
icon: faBrain, 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', title: 'Relations',
section: 'relations', section: 'relations',
@@ -224,4 +218,22 @@ export const characterElementCategory: CharacterElement[] = [
placeholder: 'Nouvelle Motivation', placeholder: 'Nouvelle Motivation',
icon: faFire, 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,
]; ];