Corrections et améliorations pour /gennpc - v1.3.0
Corrections critiques implémentées: - Remplacement du cache global mutable par ModuleCache - Binding des méthodes dans TravellerNpcDialog - Suppression des ré-exports circulaires - Validation complète des options - Correction: Duplicate export de TravellerNpcDialog et openTravellerNpcDialog - Correction: distributeSkillLevels ne supprime plus les spécialisations (ex: Pilot-Spacecraft ET Pilot-Small Craft sont maintenant conservées) Améliorations majeures: - Optimisation de l'algorithme de distribution des compétences (single-pass) - Optimisation de la génération des caractéristiques (priorité-based) - Gestion d'erreur améliorée avec TravellerNpcError - Création de TravellerNpcUtils.js avec classes utilitaires Améliorations mineures: - CSS aligné avec les styles des dialogues /commerce et /pnj - Thème clair cohérent (#f5f0e8 background, #222 text) - Fieldset, onglets, formulaires alignés sur mgt2-npc-form - Boutons et résultats stylisés comme mgt2-npc-result - Suppression des styles inline redondants dans _applyThemeStyles - Design réactif, accessibilité, impression - Tests unitaires complets pour toutes les fonctions - Version bumpée à 1.3.0 Traductions en français: - Ajout de SKILL_LABELS_FR pour toutes les compétences Traveller - Ajout de CHARACTERISTIC_LABELS_FR pour STR, DEX, END, INT, EDU, SOC - Ajout de CITIZEN_CATEGORY_LABELS_FR, EXPERIENCE_LEVEL_LABELS_FR - Ajout de ROLE_LABELS_FR, GENDER_LABELS_FR - Mise à jour de generateTravellerNpc pour utiliser les libellés français - Mise à jour du template traveller-npc-result.hbs pour afficher labelFr - Mise à jour du template traveller-npc-dialog.hbs avec libellés français - Mise à jour de TravellerNpcDialog._prepareContext pour utiliser les libellés FR Fichiers ajoutés: - scripts/utils/travellerNpcUtils.js - scripts/tests/travellerNpcGenerator.test.js Fichiers modifiés: - scripts/data/travellerNpcGenerator.js (+ traductions FR) - scripts/travellerNpcGenerator.js (+ fonctions getSkillLabelFr, getCharacteristicLabelFr) - scripts/TravellerNpcDialog.js (libellés FR dans _prepareContext) - scripts/npc.js - styles/traveller-npc.css - templates/traveller-npc-dialog.hbs - templates/traveller-npc-result.hbs - module.json Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Utilitaires
|
||||
*
|
||||
* Ce fichier contient les classes et fonctions utilitaires pour le générateur.
|
||||
*/
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
// ============================================================================
|
||||
// Classe de gestion des erreurs
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Erreur spécifique au générateur de PNJ Traveller
|
||||
*/
|
||||
export class TravellerNpcError extends Error {
|
||||
/**
|
||||
* @param {string} message - Message d'erreur
|
||||
* @param {string} code - Code d'erreur
|
||||
* @param {Object} [details={}] - Détails supplémentaires
|
||||
*/
|
||||
constructor(message, code, details = {}) {
|
||||
super(message);
|
||||
this.name = 'TravellerNpcError';
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
this.isTravellerNpcError = true;
|
||||
this.timestamp = new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une erreur à partir d'une erreur existante
|
||||
* @param {Error} error - Erreur originale
|
||||
* @param {string} code - Code d'erreur
|
||||
* @returns {TravellerNpcError}
|
||||
*/
|
||||
static from(error, code = 'UNKNOWN') {
|
||||
if (error?.isTravellerNpcError) {
|
||||
return error;
|
||||
}
|
||||
return new TravellerNpcError(
|
||||
error?.message || 'Erreur inconnue',
|
||||
code,
|
||||
{ originalError: error }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log l'erreur dans la console
|
||||
*/
|
||||
log() {
|
||||
console.error(`${MODULE_ID} | [${this.code}] ${this.message}`, {
|
||||
details: this.details,
|
||||
timestamp: this.timestamp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche une notification à l'utilisateur et log l'erreur
|
||||
*/
|
||||
notify() {
|
||||
ui.notifications?.error(`${this.code}: ${this.message}`);
|
||||
this.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une notification sans lancer d'erreur
|
||||
* @param {string} message - Message
|
||||
* @param {string} code - Code
|
||||
*/
|
||||
static warn(message, code) {
|
||||
const error = new TravellerNpcError(message, code);
|
||||
error.log();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classe de cache pour le module
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Système de cache générique pour le module
|
||||
*/
|
||||
export class ModuleCache {
|
||||
/**
|
||||
* @param {string} moduleId - ID du module
|
||||
* @param {number} [defaultTTL=300000] - Durée de vie par défaut (5 min)
|
||||
*/
|
||||
constructor(moduleId, defaultTTL = 300000) {
|
||||
this.moduleId = moduleId;
|
||||
this.defaultTTL = defaultTTL;
|
||||
this.cache = new Map();
|
||||
this.pending = new Map();
|
||||
this.timestamps = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une valeur du cache ou la fetch
|
||||
* @template T
|
||||
* @param {string} key - Clé de cache
|
||||
* @param {Function} fetchFn - Fonction de récupération
|
||||
* @param {Object} [options={}] - Options
|
||||
* @param {boolean} [options.forceRefresh=false] - Forcer le rafraîchissement
|
||||
* @param {number} [options.ttl] - TTL spécifique
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
async getOrFetch(key, fetchFn, options = {}) {
|
||||
const { forceRefresh = false, ttl = this.defaultTTL } = options;
|
||||
|
||||
// Si déjà en cache et non expiré
|
||||
if (!forceRefresh && this.cache.has(key)) {
|
||||
const timestamp = this.timestamps.get(key);
|
||||
if (Date.now() - timestamp < ttl) {
|
||||
return foundry.utils.deepClone(this.cache.get(key));
|
||||
}
|
||||
// Expiré, on le supprime
|
||||
this.clear(key);
|
||||
}
|
||||
|
||||
// Si déjà en cours de fetch pour cette clé
|
||||
if (this.pending.has(key)) {
|
||||
return this.pending.get(key);
|
||||
}
|
||||
|
||||
// Nouveau fetch
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await fetchFn();
|
||||
const cachedResult = result ? foundry.utils.deepClone(result) : null;
|
||||
this.cache.set(key, cachedResult);
|
||||
this.timestamps.set(key, Date.now());
|
||||
this.pending.delete(key);
|
||||
return foundry.utils.deepClone(cachedResult);
|
||||
} catch (error) {
|
||||
console.warn(`${this.moduleId} | Erreur de cache pour ${key}:`, error);
|
||||
this.pending.delete(key);
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
this.pending.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide une entrée du cache
|
||||
* @param {string} key - Clé à supprimer
|
||||
*/
|
||||
clear(key) {
|
||||
this.cache.delete(key);
|
||||
this.pending.delete(key);
|
||||
this.timestamps.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide tout le cache
|
||||
*/
|
||||
clearAll() {
|
||||
this.cache.clear();
|
||||
this.pending.clear();
|
||||
this.timestamps.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une clé est en cache
|
||||
* @param {string} key - Clé à vérifier
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(key) {
|
||||
if (!this.cache.has(key)) return false;
|
||||
const timestamp = this.timestamps.get(key);
|
||||
return Date.now() - timestamp < this.defaultTTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une valeur du cache sans vérification d'expiration
|
||||
* @template T
|
||||
* @param {string} key - Clé
|
||||
* @returns {T|null}
|
||||
*/
|
||||
get(key) {
|
||||
return this.cache.get(key) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Instance de cache pour le module
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Instance de cache partagée pour le générateur de PNJ Traveller
|
||||
* @type {ModuleCache}
|
||||
*/
|
||||
export const travellerNpcCache = new ModuleCache(MODULE_ID, 300000); // 5 min TTL
|
||||
|
||||
// ============================================================================
|
||||
// Codes d'erreur
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Codes d'erreur standard pour le module
|
||||
*/
|
||||
export const ERROR_CODES = {
|
||||
INVALID_OPTIONS: 'INVALID_OPTIONS',
|
||||
INVALID_ROLE: 'INVALID_ROLE',
|
||||
INVALID_CATEGORY: 'INVALID_CATEGORY',
|
||||
INVALID_EXPERIENCE: 'INVALID_EXPERIENCE',
|
||||
INVALID_GENDER: 'INVALID_GENDER',
|
||||
ACTOR_CREATION_FAILED: 'ACTOR_CREATION_FAILED',
|
||||
MGT2E_NOT_ACTIVE: 'MGT2E_NOT_ACTIVE',
|
||||
BASE_ACTOR_NOT_FOUND: 'BASE_ACTOR_NOT_FOUND',
|
||||
CACHE_ERROR: 'CACHE_ERROR',
|
||||
GENERATION_ERROR: 'GENERATION_ERROR'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Fonctions utilitaires générales
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Formate un message de debug
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function debug(message, data = {}) {
|
||||
if (game.settings.get(MODULE_ID, 'debug') || game.user?.isGM) {
|
||||
console.debug(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message de log
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function log(message, data = {}) {
|
||||
console.log(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message d'avertissement
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function warn(message, data = {}) {
|
||||
console.warn(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message d'erreur
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function error(message, data = {}) {
|
||||
console.error(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export par défaut
|
||||
// ============================================================================
|
||||
|
||||
export default {
|
||||
TravellerNpcError,
|
||||
ModuleCache,
|
||||
travellerNpcCache,
|
||||
ERROR_CODES,
|
||||
debug,
|
||||
log,
|
||||
warn,
|
||||
error
|
||||
};
|
||||
Reference in New Issue
Block a user