Remove CharacterComponent and CharacterDetail components
- Deleted `CharacterComponent` and `CharacterDetail` files from the project. - Refactored related logic to improve code maintainability and reduce redundancy.
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Sync progress interface
|
||||
*/
|
||||
export interface SyncProgress {
|
||||
isSyncing: boolean;
|
||||
pendingChanges: number;
|
||||
isOnline: boolean;
|
||||
lastError?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sync status from local database
|
||||
*/
|
||||
export async function getSyncStatus(): Promise<SyncProgress> {
|
||||
if (!window.electron) {
|
||||
return {
|
||||
isSyncing: false,
|
||||
pendingChanges: 0,
|
||||
isOnline: navigator.onLine
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electron.invoke<SyncProgress>('db:sync:status');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Failed to get sync status:', error);
|
||||
return {
|
||||
isSyncing: false,
|
||||
pendingChanges: 0,
|
||||
isOnline: navigator.onLine,
|
||||
lastError: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending changes to sync
|
||||
*/
|
||||
export async function getPendingChanges(limit: number = 100) {
|
||||
if (!window.electron) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electron.invoke<any[]>('db:sync:pending-changes', limit);
|
||||
return result || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to get pending changes:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,9 @@
|
||||
"errorBookCreate": "An error occurred while creating the book.",
|
||||
"errorBooksFetch": "An error occurred while retrieving the books.",
|
||||
"errorBookDetails": "Error fetching book details.",
|
||||
"errorUnknown": "An unknown error occurred."
|
||||
"errorUnknown": "An unknown error occurred.",
|
||||
"seriesSettings": "Series settings",
|
||||
"emptySeries": "Empty series"
|
||||
},
|
||||
"bookCard": {
|
||||
"noCoverAlt": "No cover",
|
||||
@@ -200,6 +202,11 @@
|
||||
"title": "Lyrics Generator",
|
||||
"description": "Create song lyrics in a few clicks.",
|
||||
"badge": "Lyrics"
|
||||
},
|
||||
"addSeries": {
|
||||
"title": "Create a series",
|
||||
"description": "Create a series to group multiple books.",
|
||||
"badge": "SERIES"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -350,6 +357,8 @@
|
||||
}
|
||||
},
|
||||
"worldSetting": {
|
||||
"exportSuccess": "World exported to series successfully.",
|
||||
"exportToSeries": "Export to series",
|
||||
"getWorldsError": "Error while fetching worlds.",
|
||||
"unknownError": "Unknown error.",
|
||||
"newWorldNameError": "Please enter a name for the new world.",
|
||||
@@ -358,11 +367,19 @@
|
||||
"createWorldLabel": "Create world",
|
||||
"selectWorldPlaceholder": "Select a world",
|
||||
"noWorldAvailable": "No world available.",
|
||||
"noWorldDescription": "Create your first world to develop your story's universe.",
|
||||
"newWorldPlaceholder": "New world name...",
|
||||
"search": "Search for a world...",
|
||||
"newWorld": "New world",
|
||||
"deleteTitle": "Delete world",
|
||||
"deleteMessage": "You are about to permanently delete the world \"{name}\".",
|
||||
"worldName": "World name",
|
||||
"worldNamePlaceholder": "Enter the world name",
|
||||
"worldHistory": "World history",
|
||||
"worldHistoryPlaceholder": "Describe the history of your world",
|
||||
"basicInfo": "Basic information",
|
||||
"politicsEconomy": "Politics and economy",
|
||||
"cultureLanguages": "Culture and languages",
|
||||
"politics": "Political description",
|
||||
"politicsPlaceholder": "The political description of this world...",
|
||||
"economy": "Rules and economic status",
|
||||
@@ -380,8 +397,16 @@
|
||||
"toolDisabled": "World management disabled."
|
||||
},
|
||||
"locationComponent": {
|
||||
"exportSuccess": "Location exported to series successfully.",
|
||||
"exportToSeries": "Export to series",
|
||||
"newSectionPlaceholder": "New section name",
|
||||
"addSectionLabel": "Add section",
|
||||
"search": "Search for a section...",
|
||||
"noSectionDescription": "Create your first section to organize your story's locations.",
|
||||
"newSection": "New section",
|
||||
"deleteTitle": "Delete section",
|
||||
"deleteMessage": "You are about to permanently delete the section \"{name}\".",
|
||||
"elementName": "Element name",
|
||||
"elementNamePlaceholder": "Element name",
|
||||
"elementDescriptionPlaceholder": "Element description",
|
||||
"subElementsHeading": "Sub-elements",
|
||||
@@ -392,6 +417,10 @@
|
||||
"newElementPlaceholder": "New element",
|
||||
"noSectionAvailable": "No section available.",
|
||||
"createSectionLabel": "Create section",
|
||||
"elementsCount": "{count} elements",
|
||||
"element": "Element",
|
||||
"addElement": "Add an element",
|
||||
"addSubElement": "Add a sub-element",
|
||||
"errorSectionNameEmpty": "Section name cannot be empty.",
|
||||
"errorElementNameEmpty": "Element name cannot be empty.",
|
||||
"errorSubElementNameEmpty": "Sub-element name cannot be empty.",
|
||||
@@ -416,7 +445,19 @@
|
||||
"toolEnabled": "Location management enabled.",
|
||||
"toolDisabled": "Location management disabled."
|
||||
},
|
||||
"characterCategories": {
|
||||
"none": "Select role",
|
||||
"main": "Main",
|
||||
"secondary": "Secondary",
|
||||
"recurring": "Recurring"
|
||||
},
|
||||
"characterStatus": {
|
||||
"alive": "Alive",
|
||||
"dead": "Deceased",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"characterComponent": {
|
||||
"exportSuccess": "Character exported to series successfully.",
|
||||
"errorNameRequired": "Character name is required.",
|
||||
"errorCategoryRequired": "Character role is required.",
|
||||
"successAdd": "Character added successfully.",
|
||||
@@ -435,51 +476,53 @@
|
||||
"characterDetail": {
|
||||
"back": "Back",
|
||||
"newCharacter": "New character",
|
||||
"exportToSeries": "Export to series",
|
||||
"deleteTitle": "Delete character",
|
||||
"deleteMessage": "You are about to permanently delete the character \"{name}\".",
|
||||
"basicInfo": "Basic information",
|
||||
"name": "First name",
|
||||
"namePlaceholder": "Enter a first name",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter a name",
|
||||
"lastName": "Last name",
|
||||
"lastNamePlaceholder": "Example: Smith",
|
||||
"nickname": "Nickname",
|
||||
"nicknamePlaceholder": "Nickname or alias",
|
||||
"nicknamePlaceholder": "Alias or nickname",
|
||||
"role": "Role",
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "Example: King, Captain, Doctor...",
|
||||
"titlePlaceholder": "Ex: King, Captain, Doctor...",
|
||||
"gender": "Gender",
|
||||
"genderPlaceholder": "Character's gender",
|
||||
"genderPlaceholder": "Ex: Male, Female, Non-binary",
|
||||
"age": "Age",
|
||||
"agePlaceholder": "Character's age",
|
||||
"agePlaceholder": "Ex: 25",
|
||||
"yearsOld": "years old",
|
||||
"species": "Species",
|
||||
"speciesPlaceholder": "Ex: Human, Elf, Vampire",
|
||||
"nationality": "Nationality/Origin",
|
||||
"nationalityPlaceholder": "Ex: French, Elven",
|
||||
"status": "Status",
|
||||
"residence": "Place of residence",
|
||||
"residencePlaceholder": "Where the character lives",
|
||||
"speechPattern": "Speech pattern",
|
||||
"speechPatternPlaceholder": "Verbal tics, accent, vocabulary...",
|
||||
"catchphrase": "Catchphrase",
|
||||
"catchphrasePlaceholder": "Character's recurring quote",
|
||||
"notes": "Author notes",
|
||||
"notesPlaceholder": "Personal notes, reminders...",
|
||||
"colorLabel": "Associated color",
|
||||
"colorPlaceholder": "Ex: #51AE84 or green",
|
||||
"advancedMode": "Advanced mode",
|
||||
"showAdvanced": "Show",
|
||||
"hideAdvanced": "Hide",
|
||||
"identitySection": "Extended identity",
|
||||
"voiceSection": "Character voice",
|
||||
"authorSection": "Author notes",
|
||||
"historySection": "Background",
|
||||
"biography": "Biography",
|
||||
"biographyPlaceholder": "Character biography.",
|
||||
"history": "History",
|
||||
"historyPlaceholder": "Character history...",
|
||||
"roleFull": "Role in the story",
|
||||
"roleFull": "Role",
|
||||
"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."
|
||||
"fetchAttributesError": "Error fetching attributes."
|
||||
},
|
||||
"characterList": {
|
||||
"search": "Search for a character...",
|
||||
@@ -488,12 +531,15 @@
|
||||
"unknown": "Unknown",
|
||||
"noLastName": "No last name",
|
||||
"noTitle": "No title",
|
||||
"noRole": "No role"
|
||||
"noRole": "No role",
|
||||
"noCharacters": "No characters",
|
||||
"noCharactersDescription": "Add your first character to get started."
|
||||
},
|
||||
"characterSectionElement": {
|
||||
"newItem": "New {item}"
|
||||
},
|
||||
"spellComponent": {
|
||||
"exportSuccess": "Spell exported to series successfully.",
|
||||
"enableTool": "Enable spell book",
|
||||
"enableToolDescription": "Manage the spells and magic of your universe.",
|
||||
"errorNameRequired": "Spell name is required.",
|
||||
@@ -520,6 +566,7 @@
|
||||
"spellDetail": {
|
||||
"back": "Back",
|
||||
"newSpell": "New spell",
|
||||
"exportToSeries": "Export to series",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"deleteTitle": "Delete spell",
|
||||
@@ -561,9 +608,13 @@
|
||||
},
|
||||
"spellPowerLevels": {
|
||||
"none": "None",
|
||||
"minor": "Minor",
|
||||
"moderate": "Moderate",
|
||||
"major": "Major",
|
||||
"cantrip": "Cantrip",
|
||||
"novice": "Novice",
|
||||
"apprentice": "Apprentice",
|
||||
"journeyman": "Journeyman",
|
||||
"expert": "Expert",
|
||||
"master": "Master",
|
||||
"grandmaster": "Grandmaster",
|
||||
"legendary": "Legendary",
|
||||
"divine": "Divine"
|
||||
},
|
||||
@@ -763,6 +814,26 @@
|
||||
"tip": "💡 Tip: Start with a short story to master the basics, then move to longer formats as you gain experience."
|
||||
}
|
||||
},
|
||||
"addNewSeriesForm": {
|
||||
"title": "Create a new series",
|
||||
"name": "Series name",
|
||||
"namePlaceholder": "E.g.: The Chronicles of...",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe your series...",
|
||||
"optional": "optional",
|
||||
"selectBooks": "Books to include",
|
||||
"selected": "selected",
|
||||
"noBooks": "No books available",
|
||||
"add": "Create",
|
||||
"adding": "Creating...",
|
||||
"success": "Series created successfully.",
|
||||
"error": {
|
||||
"nameMissing": "Series name is required.",
|
||||
"nameTooShort": "Name is too short. Minimum 2 characters required.",
|
||||
"nameTooLong": "Name is too long. Maximum 100 characters allowed.",
|
||||
"addingSeries": "An error occurred while creating the series."
|
||||
}
|
||||
},
|
||||
"searchBook": {
|
||||
"placeholder": "Search for a book..."
|
||||
},
|
||||
@@ -893,6 +964,13 @@
|
||||
"chapbook": "Chapbook",
|
||||
"novel": "Novel"
|
||||
},
|
||||
"bookType": {
|
||||
"short": "Short Story",
|
||||
"novelette": "Novelette",
|
||||
"novella": "Novella",
|
||||
"chapbook": "Chapbook",
|
||||
"novel": "Novel"
|
||||
},
|
||||
"chapterVersions": {
|
||||
"prompt": "Prompt",
|
||||
"draft": "Draft",
|
||||
@@ -907,10 +985,90 @@
|
||||
"world": "Worlds",
|
||||
"locations": "Locations",
|
||||
"characters": "Characters",
|
||||
"spells": "Spells",
|
||||
"spells": "Spell Book",
|
||||
"quillsense": "QuillSense (AI)",
|
||||
"objects": "Objects",
|
||||
"goals": "Goals",
|
||||
"quillsense": "QuillSense"
|
||||
"goals": "Goals"
|
||||
},
|
||||
"seriesSetting": {
|
||||
"basicInformation": "Information",
|
||||
"books": "Books",
|
||||
"characters": "Characters",
|
||||
"worlds": "Worlds",
|
||||
"locations": "Locations",
|
||||
"spells": "Spell Book",
|
||||
"backToLibrary": "Back to library",
|
||||
"errorLoading": "Error loading series",
|
||||
"deleteSeries": "Delete series",
|
||||
"deleteConfirmMessage": "This will permanently delete the series and all its elements (characters, worlds, locations, spells). Continue?",
|
||||
"deleteSuccess": "Series deleted successfully",
|
||||
"deleteError": "Error deleting series"
|
||||
},
|
||||
"seriesSettingOption": {
|
||||
"basicInformation": "Basic Information",
|
||||
"books": "Series Books",
|
||||
"characters": "Global Characters",
|
||||
"worlds": "Global Worlds",
|
||||
"locations": "Global Locations",
|
||||
"spells": "Global Spell Book",
|
||||
"notAvailable": "This option is not yet available."
|
||||
},
|
||||
"seriesBasicInformation": {
|
||||
"error": {
|
||||
"noFileSelected": "No file selected.",
|
||||
"removeCover": "Error removing cover.",
|
||||
"nameRequired": "Series name is required.",
|
||||
"update": "Error updating series information.",
|
||||
"unknown": "An unknown error occurred."
|
||||
},
|
||||
"success": {
|
||||
"update": "Series updated successfully."
|
||||
},
|
||||
"fields": {
|
||||
"name": "Series Name",
|
||||
"namePlaceholder": "E.g.: The Chronicles of...",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe your series...",
|
||||
"coverImage": "Cover Image",
|
||||
"coverImageAlt": "Current cover",
|
||||
"generateWithQuillSense": "Generate with QuillSense"
|
||||
}
|
||||
},
|
||||
"seriesBooks": {
|
||||
"addBook": "Add a book",
|
||||
"add": "Add",
|
||||
"selectBookPlaceholder": "Select a book...",
|
||||
"booksInSeries": "Books in the series",
|
||||
"noBooks": "No books in this series",
|
||||
"moveUp": "Move up",
|
||||
"moveDown": "Move down",
|
||||
"removeBook": "Remove from series",
|
||||
"error": {
|
||||
"selectBook": "Please select a book.",
|
||||
"unknown": "An unknown error occurred."
|
||||
},
|
||||
"success": {
|
||||
"saved": "Changes saved."
|
||||
}
|
||||
},
|
||||
"seriesCharacter": {
|
||||
"noCharacters": "No global characters in this series."
|
||||
},
|
||||
"seriesCard": {
|
||||
"book": "book",
|
||||
"books": "books",
|
||||
"series": "Series",
|
||||
"settings": "Series settings",
|
||||
"synced": "Synced",
|
||||
"localOnly": "Local only",
|
||||
"serverOnly": "Server only",
|
||||
"toSyncFromServer": "Download from server",
|
||||
"toSyncToServer": "Upload to server",
|
||||
"uploadError": "Error uploading series.",
|
||||
"downloadError": "Error downloading series.",
|
||||
"syncFromServerError": "Error syncing from server.",
|
||||
"syncToServerError": "Error syncing to server.",
|
||||
"refreshError": "Error refreshing series."
|
||||
},
|
||||
"basicInformationSetting": {
|
||||
"error": {
|
||||
@@ -969,12 +1127,28 @@
|
||||
"insert": "Insert"
|
||||
},
|
||||
"common": {
|
||||
"back": "Back",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
"deleting": "Deleting...",
|
||||
"edit": "Edit",
|
||||
"exportToSeries": "Export to series",
|
||||
"save": "Save",
|
||||
"unknownError": "An unknown error occurred",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"syncField": {
|
||||
"uploadSuccess": "{count} element(s) updated successfully.",
|
||||
"uploadTooltip": "Push to series",
|
||||
"downloadTooltip": "Pull from series"
|
||||
},
|
||||
"seriesImport": {
|
||||
"importButton": "Import",
|
||||
"importFromSeries": "Import from series",
|
||||
"selectElement": "Select an element"
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
"savedFailed": "Save failed",
|
||||
@@ -1026,7 +1200,9 @@
|
||||
"offlineInitError": "Error initializing offline mode",
|
||||
"syncError": "Error syncing data",
|
||||
"dbInitError": "Error initializing local database",
|
||||
"offlineError": "Error checking offline mode"
|
||||
"offlineError": "Error checking offline mode",
|
||||
"fetchBooksError": "Error fetching books",
|
||||
"fetchSeriesError": "Error fetching series"
|
||||
}
|
||||
},
|
||||
"shortStoryGenerator": {
|
||||
|
||||
@@ -154,7 +154,9 @@
|
||||
"errorBookCreate": "Une erreur est survenue lors de la création du livre.",
|
||||
"errorBooksFetch": "Une erreur est survenue lors de la récupération des livres.",
|
||||
"errorBookDetails": "Erreur lors de la récupération des détails du livre.",
|
||||
"errorUnknown": "Une erreur inconnue est survenue."
|
||||
"errorUnknown": "Une erreur inconnue est survenue.",
|
||||
"seriesSettings": "Paramètres de la série",
|
||||
"emptySeries": "Séries vides"
|
||||
},
|
||||
"bookCard": {
|
||||
"noCoverAlt": "Pas de couverture",
|
||||
@@ -200,6 +202,11 @@
|
||||
"title": "Générateur de paroles",
|
||||
"description": "Créer des paroles de chanson en quelque cliques.",
|
||||
"badge": "Lyrics"
|
||||
},
|
||||
"addSeries": {
|
||||
"title": "Créer une série",
|
||||
"description": "Créez une série pour regrouper plusieurs livres.",
|
||||
"badge": "SÉRIE"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -350,6 +357,8 @@
|
||||
}
|
||||
},
|
||||
"worldSetting": {
|
||||
"exportSuccess": "Monde exporté vers la série avec succès.",
|
||||
"exportToSeries": "Exporter vers la série",
|
||||
"getWorldsError": "Erreur lors de la récupération des mondes.",
|
||||
"unknownError": "Erreur inconnu.",
|
||||
"newWorldNameError": "Veuillez entrer un nom pour le nouveau monde.",
|
||||
@@ -358,11 +367,19 @@
|
||||
"createWorldLabel": "Créer un monde",
|
||||
"selectWorldPlaceholder": "Sélectionner un monde",
|
||||
"noWorldAvailable": "Aucun monde disponible.",
|
||||
"noWorldDescription": "Créez votre premier monde pour développer l'univers de votre histoire.",
|
||||
"newWorldPlaceholder": "Nom du nouveau monde...",
|
||||
"search": "Rechercher un monde...",
|
||||
"newWorld": "Nouveau monde",
|
||||
"deleteTitle": "Supprimer le monde",
|
||||
"deleteMessage": "Vous êtes sur le point de supprimer le monde « {name} » définitivement.",
|
||||
"worldName": "Nom du monde",
|
||||
"worldNamePlaceholder": "Entrez le nom du monde",
|
||||
"worldHistory": "Histoire du monde",
|
||||
"worldHistoryPlaceholder": "Décrivez l'histoire de votre monde",
|
||||
"basicInfo": "Informations de base",
|
||||
"politicsEconomy": "Politique et économie",
|
||||
"cultureLanguages": "Culture et langues",
|
||||
"politics": "Description politique",
|
||||
"politicsPlaceholder": "La description politique de ce monde...",
|
||||
"economy": "Règles et statut économique",
|
||||
@@ -380,8 +397,16 @@
|
||||
"toolDisabled": "Gestion des mondes désactivée."
|
||||
},
|
||||
"locationComponent": {
|
||||
"exportSuccess": "Lieu exporté vers la série avec succès.",
|
||||
"exportToSeries": "Exporter vers la série",
|
||||
"newSectionPlaceholder": "Nom de la nouvelle section",
|
||||
"addSectionLabel": "Ajouter une section",
|
||||
"search": "Rechercher une section...",
|
||||
"noSectionDescription": "Créez votre première section pour organiser les lieux de votre histoire.",
|
||||
"newSection": "Nouvelle section",
|
||||
"deleteTitle": "Supprimer la section",
|
||||
"deleteMessage": "Vous êtes sur le point de supprimer la section « {name} » définitivement.",
|
||||
"elementName": "Nom de l'élément",
|
||||
"elementNamePlaceholder": "Nom de l'élément",
|
||||
"elementDescriptionPlaceholder": "Description de l'élément",
|
||||
"subElementsHeading": "Sous-éléments",
|
||||
@@ -392,6 +417,10 @@
|
||||
"newElementPlaceholder": "Nouvel élément",
|
||||
"noSectionAvailable": "Aucune section disponible.",
|
||||
"createSectionLabel": "Créer une section",
|
||||
"elementsCount": "{count} éléments",
|
||||
"element": "Élément",
|
||||
"addElement": "Ajouter un élément",
|
||||
"addSubElement": "Ajouter un sous-élément",
|
||||
"errorSectionNameEmpty": "Le nom de la section ne peut pas être vide.",
|
||||
"errorElementNameEmpty": "Le nom de l'élément ne peut pas être vide.",
|
||||
"errorSubElementNameEmpty": "Le nom du sous-élément ne peut pas être vide.",
|
||||
@@ -417,6 +446,7 @@
|
||||
"toolDisabled": "Gestion des lieux désactivée."
|
||||
},
|
||||
"characterComponent": {
|
||||
"exportSuccess": "Personnage exporté vers la série avec succès.",
|
||||
"errorNameRequired": "Le nom du personnage est requis.",
|
||||
"errorCategoryRequired": "Le rôle du personnage est requis.",
|
||||
"successAdd": "Personnage ajouté avec succès.",
|
||||
@@ -435,20 +465,24 @@
|
||||
"characterDetail": {
|
||||
"back": "Retour",
|
||||
"newCharacter": "Nouveau personnage",
|
||||
"exportToSeries": "Exporter vers la série",
|
||||
"deleteTitle": "Supprimer le personnage",
|
||||
"deleteMessage": "Vous êtes sur le point de supprimer le personnage « {name} » définitivement.",
|
||||
"basicInfo": "Informations de base",
|
||||
"name": "Prénom",
|
||||
"namePlaceholder": "Entrer un prénom",
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "Entrer un nom",
|
||||
"lastName": "Nom de famille",
|
||||
"lastNamePlaceholder": "Exemple : Smith",
|
||||
"nickname": "Surnom",
|
||||
"nicknamePlaceholder": "Surnom ou alias du personnage",
|
||||
"nicknamePlaceholder": "Alias ou surnom",
|
||||
"role": "Rôle",
|
||||
"title": "Titre",
|
||||
"titlePlaceholder": "Exemple : Roi, Capitaine, Docteur...",
|
||||
"titlePlaceholder": "Ex: Roi, Capitaine, Docteur...",
|
||||
"gender": "Genre",
|
||||
"genderPlaceholder": "Genre du personnage",
|
||||
"genderPlaceholder": "Ex: Masculin, Féminin, Non-binaire",
|
||||
"age": "Âge",
|
||||
"agePlaceholder": "Âge du personnage",
|
||||
"agePlaceholder": "Ex: 25",
|
||||
"yearsOld": "ans",
|
||||
"historySection": "Parcours",
|
||||
"biography": "Biographie",
|
||||
"biographyPlaceholder": "La biographie du personnage.",
|
||||
@@ -488,12 +522,15 @@
|
||||
"unknown": "Inconnu",
|
||||
"noLastName": "Sans nom",
|
||||
"noTitle": "Sans titre",
|
||||
"noRole": "Sans rôle"
|
||||
"noRole": "Sans rôle",
|
||||
"noCharacters": "Aucun personnage",
|
||||
"noCharactersDescription": "Ajoutez votre premier personnage pour commencer."
|
||||
},
|
||||
"characterSectionElement": {
|
||||
"newItem": "Nouveau {item}"
|
||||
},
|
||||
"spellComponent": {
|
||||
"exportSuccess": "Sort exporté vers la série avec succès.",
|
||||
"enableTool": "Activer le grimoire de sorts",
|
||||
"enableToolDescription": "Gérez les sorts et la magie de votre univers.",
|
||||
"errorNameRequired": "Le nom du sort est requis.",
|
||||
@@ -520,6 +557,7 @@
|
||||
"spellDetail": {
|
||||
"back": "Retour",
|
||||
"newSpell": "Nouveau sort",
|
||||
"exportToSeries": "Exporter vers la série",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer",
|
||||
"deleteTitle": "Supprimer le sort",
|
||||
@@ -561,9 +599,13 @@
|
||||
},
|
||||
"spellPowerLevels": {
|
||||
"none": "Aucun",
|
||||
"minor": "Mineur",
|
||||
"moderate": "Modéré",
|
||||
"major": "Majeur",
|
||||
"cantrip": "Tour de magie",
|
||||
"novice": "Novice",
|
||||
"apprentice": "Apprenti",
|
||||
"journeyman": "Compagnon",
|
||||
"expert": "Expert",
|
||||
"master": "Maître",
|
||||
"grandmaster": "Grand maître",
|
||||
"legendary": "Légendaire",
|
||||
"divine": "Divin"
|
||||
},
|
||||
@@ -970,9 +1012,15 @@
|
||||
"insert": "Insérer"
|
||||
},
|
||||
"common": {
|
||||
"back": "Retour",
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"create": "Créer",
|
||||
"delete": "Supprimer",
|
||||
"deleting": "Suppression...",
|
||||
"edit": "Modifier",
|
||||
"exportToSeries": "Exporter vers la série",
|
||||
"save": "Enregistrer",
|
||||
"unknownError": "Une erreur inconnue est survenue",
|
||||
"loading": "Chargement..."
|
||||
},
|
||||
@@ -1027,7 +1075,9 @@
|
||||
"offlineInitError": "Erreur lors de l'initialisation du mode hors ligne",
|
||||
"syncError": "Erreur lors de la synchronisation des données",
|
||||
"dbInitError": "Erreur lors de l'initialisation de la base de données locale",
|
||||
"offlineError": "Erreur lors de la vérification du mode hors ligne"
|
||||
"offlineError": "Erreur lors de la vérification du mode hors ligne",
|
||||
"fetchBooksError": "Erreur lors de la récupération des livres",
|
||||
"fetchSeriesError": "Erreur lors de la récupération des séries"
|
||||
}
|
||||
},
|
||||
"shortStoryGenerator": {
|
||||
@@ -1139,6 +1189,134 @@
|
||||
"deleteLocalWarning": "Attention : Cette action supprimera le livre du serveur ET de votre appareil. Cette action est irréversible.",
|
||||
"errorUnknown": "Une erreur inconnue est survenue lors de la suppression du livre."
|
||||
},
|
||||
"characterCategories": {
|
||||
"none": "Sélectionner son rôle",
|
||||
"main": "Principal",
|
||||
"secondary": "Secondaire",
|
||||
"recurring": "Récurrent"
|
||||
},
|
||||
"characterStatus": {
|
||||
"alive": "Vivant",
|
||||
"dead": "Décédé",
|
||||
"unknown": "Inconnu"
|
||||
},
|
||||
"addNewSeriesForm": {
|
||||
"title": "Créer une nouvelle série",
|
||||
"name": "Nom de la série",
|
||||
"namePlaceholder": "Ex: Les Chroniques de...",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Décrivez votre série...",
|
||||
"optional": "optionnel",
|
||||
"selectBooks": "Livres à inclure",
|
||||
"selected": "sélectionné(s)",
|
||||
"noBooks": "Aucun livre disponible",
|
||||
"add": "Créer",
|
||||
"adding": "Création...",
|
||||
"success": "Série créée avec succès.",
|
||||
"error": {
|
||||
"nameMissing": "Le nom de la série est requis.",
|
||||
"nameTooShort": "Le nom est trop court. Minimum 2 caractères requis.",
|
||||
"nameTooLong": "Le nom est trop long. Maximum 100 caractères autorisés.",
|
||||
"addingSeries": "Une erreur est survenue lors de la création de la série."
|
||||
}
|
||||
},
|
||||
"seriesSetting": {
|
||||
"basicInformation": "Informations",
|
||||
"books": "Œuvres",
|
||||
"characters": "Personnages",
|
||||
"worlds": "Mondes",
|
||||
"locations": "Emplacements",
|
||||
"spells": "Grimoire",
|
||||
"backToLibrary": "Retour à la bibliothèque",
|
||||
"errorLoading": "Erreur lors du chargement de la série",
|
||||
"deleteSeries": "Supprimer la série",
|
||||
"deleteConfirmMessage": "Cette action supprimera définitivement la série et tous ses éléments (personnages, mondes, lieux, sorts). Continuer?",
|
||||
"deleteSuccess": "Série supprimée avec succès",
|
||||
"deleteError": "Erreur lors de la suppression de la série"
|
||||
},
|
||||
"seriesSettingOption": {
|
||||
"basicInformation": "Informations de base",
|
||||
"books": "Œuvres de la série",
|
||||
"characters": "Les personnages",
|
||||
"worlds": "Gérer les mondes",
|
||||
"locations": "Vos emplacements",
|
||||
"spells": "Grimoire de sorts",
|
||||
"notAvailable": "Cette option n'est pas encore disponible."
|
||||
},
|
||||
"seriesBasicInformation": {
|
||||
"error": {
|
||||
"noFileSelected": "Aucun fichier sélectionné.",
|
||||
"removeCover": "Erreur lors de la suppression de la couverture.",
|
||||
"nameRequired": "Le nom de la série est obligatoire.",
|
||||
"update": "Erreur lors de la mise à jour des informations de la série.",
|
||||
"unknown": "Une erreur inconnue est survenue."
|
||||
},
|
||||
"success": {
|
||||
"update": "Série mise à jour avec succès."
|
||||
},
|
||||
"fields": {
|
||||
"name": "Nom de la série",
|
||||
"namePlaceholder": "Ex: Les Chroniques de...",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Décrivez votre série...",
|
||||
"coverImage": "Image couverture",
|
||||
"coverImageAlt": "Couverture actuelle",
|
||||
"generateWithQuillSense": "Générer avec QuillSense"
|
||||
}
|
||||
},
|
||||
"seriesBooks": {
|
||||
"addBook": "Ajouter un livre",
|
||||
"add": "Ajouter",
|
||||
"selectBookPlaceholder": "Sélectionner un livre...",
|
||||
"booksInSeries": "Livres dans la série",
|
||||
"noBooks": "Aucun livre dans cette série",
|
||||
"moveUp": "Monter",
|
||||
"moveDown": "Descendre",
|
||||
"removeBook": "Retirer de la série",
|
||||
"error": {
|
||||
"selectBook": "Veuillez sélectionner un livre.",
|
||||
"unknown": "Une erreur inconnue est survenue."
|
||||
},
|
||||
"success": {
|
||||
"saved": "Changements enregistrés."
|
||||
}
|
||||
},
|
||||
"seriesCharacter": {
|
||||
"noCharacters": "Aucun personnage global dans cette série."
|
||||
},
|
||||
"seriesCard": {
|
||||
"book": "livre",
|
||||
"books": "livres",
|
||||
"series": "Série",
|
||||
"settings": "Paramètres de la série",
|
||||
"synced": "Synchronisé",
|
||||
"localOnly": "Local uniquement",
|
||||
"serverOnly": "Sur le serveur uniquement",
|
||||
"toSyncFromServer": "Télécharger depuis le serveur",
|
||||
"toSyncToServer": "Envoyer vers le serveur",
|
||||
"uploadError": "Erreur lors du téléversement de la série.",
|
||||
"downloadError": "Erreur lors du téléchargement de la série.",
|
||||
"syncFromServerError": "Erreur lors de la synchronisation depuis le serveur.",
|
||||
"syncToServerError": "Erreur lors de la synchronisation vers le serveur.",
|
||||
"refreshError": "Erreur lors du rafraîchissement des séries."
|
||||
},
|
||||
"bookType": {
|
||||
"short": "Nouvelle",
|
||||
"novelette": "Novelette",
|
||||
"novella": "Novella",
|
||||
"chapbook": "Chapbook",
|
||||
"novel": "Roman"
|
||||
},
|
||||
"syncField": {
|
||||
"uploadSuccess": "{count} élément(s) mis à jour avec succès.",
|
||||
"uploadTooltip": "Envoyer vers la série",
|
||||
"downloadTooltip": "Récupérer depuis la série"
|
||||
},
|
||||
"seriesImport": {
|
||||
"importButton": "Importer",
|
||||
"importFromSeries": "Importer depuis la série",
|
||||
"selectElement": "Sélectionner un élément"
|
||||
},
|
||||
"quillSenseSetting": {
|
||||
"title": "Paramètres QuillSense",
|
||||
"description": "Gérez les fonctionnalités d'intelligence artificielle pour ce livre.",
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface BookProps {
|
||||
title: string;
|
||||
author?: Author;
|
||||
serie?: number;
|
||||
seriesId?: string | null;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
publicationDate?: string;
|
||||
|
||||
@@ -23,28 +23,16 @@ import {SelectBoxProps} from "@/shared/interface";
|
||||
type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none';
|
||||
|
||||
export const characterCategories: SelectBoxProps[] = [
|
||||
{
|
||||
value: 'none',
|
||||
label: 'Sélectionner son rôle',
|
||||
},
|
||||
{
|
||||
value: 'main',
|
||||
label: 'Principal',
|
||||
},
|
||||
{
|
||||
value: 'secondary',
|
||||
label: 'Secondaire',
|
||||
},
|
||||
{
|
||||
value: 'recurring',
|
||||
label: 'Récurrent',
|
||||
},
|
||||
{value: 'none', label: 'characterCategories.none'},
|
||||
{value: 'main', label: 'characterCategories.main'},
|
||||
{value: 'secondary', label: 'characterCategories.secondary'},
|
||||
{value: 'recurring', label: 'characterCategories.recurring'},
|
||||
];
|
||||
|
||||
export const characterStatus: SelectBoxProps[] = [
|
||||
{value: 'alive', label: 'Vivant'},
|
||||
{value: 'dead', label: 'Décédé'},
|
||||
{value: 'unknown', label: 'Inconnu'},
|
||||
{value: 'alive', label: 'characterStatus.alive'},
|
||||
{value: 'dead', label: 'characterStatus.dead'},
|
||||
{value: 'unknown', label: 'characterStatus.unknown'},
|
||||
];
|
||||
|
||||
export interface Relation {
|
||||
@@ -68,7 +56,7 @@ export interface CharacterProps {
|
||||
name: string;
|
||||
lastName: string;
|
||||
nickname: string;
|
||||
age: string;
|
||||
age: number | null;
|
||||
gender: string;
|
||||
species: string;
|
||||
nationality: string;
|
||||
@@ -102,6 +90,7 @@ export interface CharacterProps {
|
||||
residence?: string;
|
||||
notes?: string;
|
||||
color?: string;
|
||||
seriesCharacterId?: string | null;
|
||||
}
|
||||
|
||||
export interface CharacterListResponse {
|
||||
|
||||
170
lib/models/Series.ts
Normal file
170
lib/models/Series.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
export interface SeriesProps {
|
||||
id: string | null;
|
||||
name: string;
|
||||
description: string;
|
||||
coverImage: string | null;
|
||||
}
|
||||
|
||||
export interface SeriesBookProps {
|
||||
bookId: string;
|
||||
title: string;
|
||||
order: number;
|
||||
coverImage: string | null;
|
||||
}
|
||||
|
||||
export interface SeriesDetailResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
coverImage: string | null;
|
||||
books: SeriesBookProps[];
|
||||
}
|
||||
|
||||
export interface SeriesAddResponse {
|
||||
seriesId: string;
|
||||
}
|
||||
|
||||
export interface SeriesUpdateResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface SeriesListItemProps {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
coverImage: string | null;
|
||||
bookCount: number;
|
||||
bookIds: string[];
|
||||
}
|
||||
|
||||
// Personnages de série
|
||||
export interface SeriesCharacterListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
lastName: string | null;
|
||||
category: string;
|
||||
role: string | null;
|
||||
color: string | null;
|
||||
image: string | null;
|
||||
}
|
||||
|
||||
export interface SeriesCharacterDetailResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
lastName: string | null;
|
||||
nickname: string | null;
|
||||
age: number | 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;
|
||||
attributes?: SeriesCharacterAttribute[];
|
||||
}
|
||||
|
||||
export interface SeriesCharacterAttribute {
|
||||
id: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type SeriesCharacterProps = SeriesCharacterDetailResponse;
|
||||
|
||||
// Mondes de série
|
||||
export interface SeriesWorldElementItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SeriesWorldListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
history: string;
|
||||
politics: string;
|
||||
economy: string;
|
||||
religion: string;
|
||||
languages: string;
|
||||
laws: SeriesWorldElementItem[];
|
||||
biomes: SeriesWorldElementItem[];
|
||||
issues: SeriesWorldElementItem[];
|
||||
customs: SeriesWorldElementItem[];
|
||||
kingdoms: SeriesWorldElementItem[];
|
||||
climate: SeriesWorldElementItem[];
|
||||
resources: SeriesWorldElementItem[];
|
||||
wildlife: SeriesWorldElementItem[];
|
||||
arts: SeriesWorldElementItem[];
|
||||
ethnicGroups: SeriesWorldElementItem[];
|
||||
socialClasses: SeriesWorldElementItem[];
|
||||
importantCharacters: SeriesWorldElementItem[];
|
||||
}
|
||||
|
||||
export type SeriesWorldProps = SeriesWorldListItem;
|
||||
|
||||
export interface SeriesWorldElement {
|
||||
id: string;
|
||||
type: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Lieux de série
|
||||
export interface SeriesLocationSubElement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SeriesLocationElement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
subElements: SeriesLocationSubElement[];
|
||||
}
|
||||
|
||||
export interface SeriesLocationItem {
|
||||
id: string;
|
||||
name: string;
|
||||
elements: SeriesLocationElement[];
|
||||
}
|
||||
|
||||
// Sorts de série (Grimoire)
|
||||
export interface SeriesSpellTag {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
export interface SeriesSpellListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface SeriesSpellListResponse {
|
||||
spells: SeriesSpellListItem[];
|
||||
tags: SeriesSpellTag[];
|
||||
}
|
||||
|
||||
export interface SeriesSpellDetailResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export interface SpellProps {
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Pour POST /spell/add et PUT /spell/update
|
||||
@@ -34,6 +35,7 @@ export interface SpellPropsPost {
|
||||
components?: string | null;
|
||||
limitations?: string | null;
|
||||
notes?: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Item dans la liste (GET /spell/list)
|
||||
@@ -42,6 +44,7 @@ export interface SpellListItem {
|
||||
name: string;
|
||||
description: string;
|
||||
tags: SpellTagProps[]; // Tags résolus (pas les IDs)
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Réponse de GET /spell/list
|
||||
@@ -62,6 +65,7 @@ export interface SpellEditState {
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
export const initialSpellState: SpellEditState = {
|
||||
@@ -74,6 +78,7 @@ export const initialSpellState: SpellEditState = {
|
||||
components: null,
|
||||
limitations: null,
|
||||
notes: null,
|
||||
seriesSpellId: null,
|
||||
};
|
||||
|
||||
export const spellPowerLevels: SelectBoxProps[] = [
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface SyncedBook {
|
||||
type: string;
|
||||
title: string;
|
||||
subTitle: string | null;
|
||||
seriesId: string | null;
|
||||
lastUpdate: number;
|
||||
chapters: SyncedChapter[];
|
||||
characters: SyncedCharacter[];
|
||||
|
||||
280
lib/models/SyncedSeries.ts
Normal file
280
lib/models/SyncedSeries.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Lightweight sync structures for series comparison.
|
||||
* These interfaces mirror the backend SyncedSeries* types from Book.ts
|
||||
* but are used in the frontend for sync status detection.
|
||||
*/
|
||||
|
||||
export interface SyncedSeriesBook {
|
||||
bookId: string;
|
||||
order: number;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeriesCharacterAttribute {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeriesCharacter {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
attributes: SyncedSeriesCharacterAttribute[];
|
||||
}
|
||||
|
||||
export interface SyncedSeriesWorldElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeriesWorld {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedSeriesWorldElement[];
|
||||
}
|
||||
|
||||
export interface SyncedSeriesLocationSubElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeriesLocationElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
subElements: SyncedSeriesLocationSubElement[];
|
||||
}
|
||||
|
||||
export interface SyncedSeriesLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedSeriesLocationElement[];
|
||||
}
|
||||
|
||||
export interface SyncedSeriesSpell {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeriesSpellTag {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSeries {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
lastUpdate: number;
|
||||
books: SyncedSeriesBook[];
|
||||
characters: SyncedSeriesCharacter[];
|
||||
worlds: SyncedSeriesWorld[];
|
||||
locations: SyncedSeriesLocation[];
|
||||
spells: SyncedSeriesSpell[];
|
||||
spellTags: SyncedSeriesSpellTag[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison result containing IDs of changed entities.
|
||||
* Used for partial synchronization - only changed entities are transferred.
|
||||
*/
|
||||
export interface SeriesSyncCompare {
|
||||
id: string;
|
||||
books: string[];
|
||||
characters: string[];
|
||||
characterAttributes: string[];
|
||||
worlds: string[];
|
||||
worldElements: string[];
|
||||
locations: string[];
|
||||
locationElements: string[];
|
||||
locationSubElements: string[];
|
||||
spells: string[];
|
||||
spellTags: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two versions of a series to find changed entities.
|
||||
* The "newer" series is compared against the "older" one to find
|
||||
* entities that have been added or modified.
|
||||
*
|
||||
* @param newerSeries - The series version with potentially newer data
|
||||
* @param olderSeries - The series version to compare against
|
||||
* @returns SeriesSyncCompare with IDs of changed entities, or null if no changes
|
||||
*/
|
||||
export function compareSeriesSyncs(newerSeries: SyncedSeries, olderSeries: SyncedSeries): SeriesSyncCompare | null {
|
||||
const changedBookIds: string[] = [];
|
||||
const changedCharacterIds: string[] = [];
|
||||
const changedCharacterAttributeIds: string[] = [];
|
||||
const changedWorldIds: string[] = [];
|
||||
const changedWorldElementIds: string[] = [];
|
||||
const changedLocationIds: string[] = [];
|
||||
const changedLocationElementIds: string[] = [];
|
||||
const changedLocationSubElementIds: string[] = [];
|
||||
const changedSpellIds: string[] = [];
|
||||
const changedSpellTagIds: string[] = [];
|
||||
|
||||
// Compare books
|
||||
newerSeries.books.forEach((newerBook: SyncedSeriesBook): void => {
|
||||
const olderBook: SyncedSeriesBook | undefined = olderSeries.books.find(
|
||||
(book: SyncedSeriesBook): boolean => book.bookId === newerBook.bookId
|
||||
);
|
||||
if (!olderBook || newerBook.lastUpdate > olderBook.lastUpdate) {
|
||||
changedBookIds.push(newerBook.bookId);
|
||||
}
|
||||
});
|
||||
|
||||
// Compare characters and their attributes
|
||||
newerSeries.characters.forEach((newerCharacter: SyncedSeriesCharacter): void => {
|
||||
const olderCharacter: SyncedSeriesCharacter | undefined = olderSeries.characters.find(
|
||||
(character: SyncedSeriesCharacter): boolean => character.id === newerCharacter.id
|
||||
);
|
||||
|
||||
if (!olderCharacter) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
newerCharacter.attributes.forEach((attr: SyncedSeriesCharacterAttribute): void => {
|
||||
changedCharacterAttributeIds.push(attr.id);
|
||||
});
|
||||
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
} else {
|
||||
// Check attributes even if character hasn't changed
|
||||
newerCharacter.attributes.forEach((newerAttr: SyncedSeriesCharacterAttribute): void => {
|
||||
const olderAttr: SyncedSeriesCharacterAttribute | undefined = olderCharacter.attributes.find(
|
||||
(attr: SyncedSeriesCharacterAttribute): boolean => attr.id === newerAttr.id
|
||||
);
|
||||
if (!olderAttr || newerAttr.lastUpdate > olderAttr.lastUpdate) {
|
||||
changedCharacterAttributeIds.push(newerAttr.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Compare worlds and their elements
|
||||
newerSeries.worlds.forEach((newerWorld: SyncedSeriesWorld): void => {
|
||||
const olderWorld: SyncedSeriesWorld | undefined = olderSeries.worlds.find(
|
||||
(world: SyncedSeriesWorld): boolean => world.id === newerWorld.id
|
||||
);
|
||||
|
||||
if (!olderWorld) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
newerWorld.elements.forEach((element: SyncedSeriesWorldElement): void => {
|
||||
changedWorldElementIds.push(element.id);
|
||||
});
|
||||
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
} else {
|
||||
// Check elements even if world hasn't changed
|
||||
newerWorld.elements.forEach((newerElement: SyncedSeriesWorldElement): void => {
|
||||
const olderElement: SyncedSeriesWorldElement | undefined = olderWorld.elements.find(
|
||||
(element: SyncedSeriesWorldElement): boolean => element.id === newerElement.id
|
||||
);
|
||||
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) {
|
||||
changedWorldElementIds.push(newerElement.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Compare locations, their elements, and sub-elements
|
||||
newerSeries.locations.forEach((newerLocation: SyncedSeriesLocation): void => {
|
||||
const olderLocation: SyncedSeriesLocation | undefined = olderSeries.locations.find(
|
||||
(location: SyncedSeriesLocation): boolean => location.id === newerLocation.id
|
||||
);
|
||||
|
||||
if (!olderLocation) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
newerLocation.elements.forEach((element: SyncedSeriesLocationElement): void => {
|
||||
changedLocationElementIds.push(element.id);
|
||||
element.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
|
||||
changedLocationSubElementIds.push(subElement.id);
|
||||
});
|
||||
});
|
||||
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
} else {
|
||||
// Check elements
|
||||
newerLocation.elements.forEach((newerElement: SyncedSeriesLocationElement): void => {
|
||||
const olderElement: SyncedSeriesLocationElement | undefined = olderLocation.elements.find(
|
||||
(element: SyncedSeriesLocationElement): boolean => element.id === newerElement.id
|
||||
);
|
||||
|
||||
if (!olderElement) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
newerElement.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
|
||||
changedLocationSubElementIds.push(subElement.id);
|
||||
});
|
||||
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
} else {
|
||||
// Check sub-elements
|
||||
newerElement.subElements.forEach((newerSubElement: SyncedSeriesLocationSubElement): void => {
|
||||
const olderSubElement: SyncedSeriesLocationSubElement | undefined = olderElement.subElements.find(
|
||||
(subElement: SyncedSeriesLocationSubElement): boolean => subElement.id === newerSubElement.id
|
||||
);
|
||||
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) {
|
||||
changedLocationSubElementIds.push(newerSubElement.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Compare spells
|
||||
newerSeries.spells.forEach((newerSpell: SyncedSeriesSpell): void => {
|
||||
const olderSpell: SyncedSeriesSpell | undefined = olderSeries.spells.find(
|
||||
(spell: SyncedSeriesSpell): boolean => spell.id === newerSpell.id
|
||||
);
|
||||
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) {
|
||||
changedSpellIds.push(newerSpell.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Compare spell tags
|
||||
newerSeries.spellTags.forEach((newerTag: SyncedSeriesSpellTag): void => {
|
||||
const olderTag: SyncedSeriesSpellTag | undefined = olderSeries.spellTags.find(
|
||||
(tag: SyncedSeriesSpellTag): boolean => tag.id === newerTag.id
|
||||
);
|
||||
if (!olderTag || newerTag.lastUpdate > olderTag.lastUpdate) {
|
||||
changedSpellTagIds.push(newerTag.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Check if there are any changes
|
||||
const hasChanges: boolean =
|
||||
changedBookIds.length > 0 ||
|
||||
changedCharacterIds.length > 0 ||
|
||||
changedCharacterAttributeIds.length > 0 ||
|
||||
changedWorldIds.length > 0 ||
|
||||
changedWorldElementIds.length > 0 ||
|
||||
changedLocationIds.length > 0 ||
|
||||
changedLocationElementIds.length > 0 ||
|
||||
changedLocationSubElementIds.length > 0 ||
|
||||
changedSpellIds.length > 0 ||
|
||||
changedSpellTagIds.length > 0;
|
||||
|
||||
if (!hasChanges) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: newerSeries.id,
|
||||
books: changedBookIds,
|
||||
characters: changedCharacterIds,
|
||||
characterAttributes: changedCharacterAttributeIds,
|
||||
worlds: changedWorldIds,
|
||||
worldElements: changedWorldElementIds,
|
||||
locations: changedLocationIds,
|
||||
locationElements: changedLocationElementIds,
|
||||
locationSubElements: changedLocationSubElementIds,
|
||||
spells: changedSpellIds,
|
||||
spellTags: changedSpellTagIds
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,11 @@ export default class System{
|
||||
return pattern.test(input);
|
||||
}
|
||||
|
||||
public static timeStampInSeconds(): number {
|
||||
const date: number = new Date().getTime();
|
||||
return Math.floor(date / 1000);
|
||||
}
|
||||
|
||||
public static formatHTMLContent(htmlContent: string): string {
|
||||
return htmlContent
|
||||
.replace(/<h1>/g, '<h1 style="color: #FFFFFF; text-indent: 5px; font-size: 28px; font-weight: bold; text-align: left; margin-vertical: 10px;">')
|
||||
|
||||
@@ -11,8 +11,14 @@ import {
|
||||
faSnowflake,
|
||||
faUserCog,
|
||||
faUserFriends,
|
||||
IconDefinition,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {ElementSection} from "@/components/book/settings/world/WorldSetting";
|
||||
|
||||
export interface ElementSection {
|
||||
title: string;
|
||||
section: keyof WorldProps;
|
||||
icon: IconDefinition;
|
||||
}
|
||||
|
||||
export interface WorldElement {
|
||||
id: string;
|
||||
@@ -40,6 +46,7 @@ export interface WorldProps {
|
||||
ethnicGroups: WorldElement[];
|
||||
socialClasses: WorldElement[];
|
||||
importantCharacters: WorldElement[];
|
||||
seriesWorldId?: string | null;
|
||||
}
|
||||
|
||||
export interface WorldListResponse {
|
||||
|
||||
Reference in New Issue
Block a user