Enhance MessagesDialog component to support HTML content and improve parameter extraction

- Updated notification description rendering to allow HTML content using v-html directive.
- Refactored formatBody method to better handle JSON formatted notifications and extract parameters from nested structures.
- Introduced new method for extracting parameters from value objects, improving compatibility with various notification types.
- Enhanced description formatting to include details from effects, providing richer user feedback in notifications.
This commit is contained in:
Torsten Schulz (local)
2026-01-07 12:09:25 +01:00
parent d42e1da14b
commit 511df52c3c

View File

@@ -16,7 +16,7 @@
<li v-for="n in messages" :key="n.id" :class="{ unread: !n.shown }">
<div class="body">
<div v-if="formatBody(n).title" class="notification-title">{{ formatBody(n).title }}</div>
<div class="notification-description">{{ formatBody(n).description || formatBody(n) }}</div>
<div class="notification-description" v-html="formatBody(n).description || formatBody(n)"></div>
</div>
<div class="footer">
<span>{{ formatDate(n.createdAt) }}</span>
@@ -133,54 +133,52 @@ export default {
} catch { return dt; }
},
formatBody(n) {
// Wenn die Notification bereits title und description hat (z.B. von WebSocket Events)
if (n.title && n.description) {
// Parameter aus effects oder anderen Feldern extrahieren
const params = this.extractParams(n);
return {
title: this.interpolateString(n.title, params),
description: this.interpolateString(n.description, params)
};
}
let raw = n.tr || '';
let parsed = null;
let value = null;
let key = raw;
let params = {};
// 1) JSON-Format unterstützen: {"tr":"random_event.windfall","amount":1000,"characterName":"Max"}
// 1) Parse JSON-Format: {"tr":"random_event.character_illness","value":{...}}
if (typeof raw === 'string') {
const trimmed = raw.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
try {
const parsed = JSON.parse(trimmed);
parsed = JSON.parse(trimmed);
if (parsed && parsed.tr) {
raw = parsed.tr;
key = parsed.tr;
// Alle anderen Felder als Parameter verwenden und formatieren
params = this.formatParams({ ...parsed });
delete params.tr;
// Merge in params extracted from nested structures (effects, character ids)
try {
const extracted = this.extractParams({ ...n, ...parsed, characterName: parsed.characterName || parsed.character_name || n.characterName || n.character_name });
for (const [k, v] of Object.entries(extracted || {})) {
if (!params.hasOwnProperty(k) || params[k] === undefined || params[k] === null || params[k] === '') {
params[k] = v;
}
}
} catch (e) {
// ignore extraction errors
}
value = parsed.value || {};
// Extrahiere Parameter aus value und effects
params = this.extractParamsFromValue(value, n);
}
} catch (e) {
// bei Parse-Fehler einfach weiter unten mit dem Rohwert arbeiten
// Bei Parse-Fehler: Alte Struktur unterstützen
try {
parsed = JSON.parse(trimmed);
if (parsed && parsed.tr) {
key = parsed.tr;
params = this.formatParams({ ...parsed });
delete params.tr;
}
} catch (e2) {
// Ignore parse errors
}
}
}
}
// 2) Schlüssel normalisieren:
// - wenn bereits ein voller i18n-Key wie "falukant.notifications.production.overproduction",
// dann direkt verwenden
// - sonst in den Namespace "falukant.notifications." hängen
// 2) Wenn value.title und value.description vorhanden sind, verwende diese
if (value && value.title && value.description) {
// Parameter aus effects extrahieren und formatieren
const formattedParams = this.formatParams(params);
return {
title: this.interpolateString(value.title, formattedParams),
description: this.formatDescriptionWithEffects(value.description, value.effects || [], formattedParams)
};
}
// 3) Schlüssel normalisieren
if (typeof key === 'string') {
const trimmedKey = key.trim();
if (trimmedKey.startsWith('falukant.')) {
@@ -190,21 +188,35 @@ export default {
}
}
// 3) Prüfe, ob es sich um ein random_event handelt mit title/description Struktur
// 4) Prüfe, ob es sich um ein random_event handelt mit title/description Struktur
if (key.startsWith('falukant.notifications.random_event.')) {
const eventId = key.replace('falukant.notifications.random_event.', '');
const eventKey = `falukant.notifications.random_event.${eventId}`;
try {
const titleKey = `${eventKey}.title`;
const descKey = `${eventKey}.description`;
// If no params were parsed from JSON, try to extract them from the notification (effects, character_id, etc.)
// Wenn keine Parameter extrahiert wurden, versuche es aus der Notification
if ((!params || Object.keys(params).length === 0) && n) {
params = this.extractParams(n) || {};
}
// Wenn value vorhanden ist, verwende effects für Details
if (value && value.effects) {
params = this.extractParamsFromValue(value, n);
}
const formattedParams = this.formatParams(params);
if (this.$te(titleKey) && this.$te(descKey)) {
const title = this.$t(titleKey, params);
const description = this.$t(descKey, params);
const title = this.$t(titleKey, formattedParams);
let description = this.$t(descKey, formattedParams);
// Füge Effect-Details hinzu, falls vorhanden
if (value && value.effects) {
description = this.formatDescriptionWithEffects(description, value.effects, formattedParams);
}
return { title, description };
}
} catch (e) {
@@ -213,42 +225,64 @@ export default {
}
// Fallback: Alte Methode für andere Notification-Typen
return this.$t(key, params);
const formattedParams = this.formatParams(params);
return this.$t(key, formattedParams);
},
formatParams(params) {
const formatted = {};
// Geldbeträge formatieren
if (params.amount !== undefined) {
if (params.amount !== undefined && params.amount !== null) {
formatted.amount = this.formatMoney(params.amount);
}
if (params.absolute !== undefined) {
if (params.absolute !== undefined && params.absolute !== null) {
formatted.amount = this.formatMoney(params.absolute);
}
if (params.percent !== undefined) {
formatted.amount = `${params.percent > 0 ? '+' : ''}${params.percent.toFixed(1)}%`;
if (params.percent !== undefined && params.percent !== null) {
formatted.percent = `${params.percent > 0 ? '+' : ''}${params.percent.toFixed(1)}%`;
}
// Gesundheit formatieren
if (params.change !== undefined) {
if (params.change !== undefined && params.change !== null) {
formatted.healthChange = params.change > 0 ? `+${params.change}` : `${params.change}`;
formatted.change = formatted.healthChange;
}
if (params.healthChange !== undefined) {
formatted.healthChange = params.healthChange > 0 ? `+${params.healthChange}` : `${params.healthChange}`;
if (params.healthChange !== undefined && params.healthChange !== null && !formatted.healthChange) {
formatted.healthChange = typeof params.healthChange === 'string'
? params.healthChange
: (params.healthChange > 0 ? `+${params.healthChange}` : `${params.healthChange}`);
}
// Schaden formatieren
if (params.inventory_damage_percent !== undefined) {
// Charakternamen
if (params.characterName) {
formatted.characterName = params.characterName;
}
if (params.character_first_name || params.character_last_name) {
const firstName = params.character_first_name || '';
const lastName = params.character_last_name || '';
formatted.characterName = `${firstName} ${lastName}`.trim() || formatted.characterName;
}
// Regions-Informationen
if (params.regionName) {
formatted.regionName = params.regionName;
}
if (params.region_id) {
formatted.region_id = params.region_id;
}
// Schaden formatieren (für Kompatibilität mit alter Struktur)
if (params.inventory_damage_percent !== undefined && params.inventory_damage_percent !== null) {
formatted.damagePercent = ` Lagerbestand beschädigt: ${params.inventory_damage_percent.toFixed(1)}%.`;
}
if (params.storage_destruction_percent !== undefined) {
if (params.storage_destruction_percent !== undefined && params.storage_destruction_percent !== null) {
formatted.destructionPercent = ` Lager zerstört: ${params.storage_destruction_percent.toFixed(1)}%.`;
}
// Alle anderen Parameter übernehmen
for (const [key, value] of Object.entries(params)) {
if (!formatted.hasOwnProperty(key) && key !== 'tr') {
if (!formatted.hasOwnProperty(key) && key !== 'tr' && value !== undefined && value !== null) {
formatted[key] = value;
}
}
@@ -256,10 +290,72 @@ export default {
return formatted;
},
extractParamsFromValue(value, n) {
const params = {};
// Charakternamen aus value extrahieren
if (value.character_first_name || value.character_last_name) {
const firstName = value.character_first_name || '';
const lastName = value.character_last_name || '';
params.characterName = `${firstName} ${lastName}`.trim() || value.character_id ? `#${value.character_id}` : null;
}
if (value.character_id) {
params.character_id = value.character_id;
}
// Regions-Informationen
if (value.region_id) {
params.region_id = value.region_id;
}
if (value.regionName) {
params.regionName = value.regionName;
}
// Parameter aus effects extrahieren
if (value.effects && Array.isArray(value.effects)) {
for (const effect of value.effects) {
if (effect.type === 'character_health_change') {
// Charakternamen aus Effect haben Vorrang
if (effect.character_first_name || effect.character_last_name) {
const firstName = effect.character_first_name || '';
const lastName = effect.character_last_name || '';
params.characterName = `${firstName} ${lastName}`.trim();
}
if (effect.character_id) {
params.character_id = effect.character_id;
}
if (effect.change !== undefined) {
params.change = effect.change;
params.healthChange = effect.change > 0 ? `+${effect.change}` : `${effect.change}`;
}
} else if (effect.type === 'character_death') {
if (effect.character_first_name || effect.character_last_name) {
const firstName = effect.character_first_name || '';
const lastName = effect.character_last_name || '';
params.characterName = `${firstName} ${lastName}`.trim();
}
if (effect.character_id) {
params.character_id = effect.character_id;
}
}
}
}
// Fallback: Charakternamen aus Notification-Spalte
if (!params.characterName && n.characterName) {
params.characterName = n.characterName;
}
if (!params.characterName && n.character_id) {
params.characterName = `#${n.character_id}`;
}
return params;
},
extractParams(n) {
const params = {};
// Parameter aus effects extrahieren
// Parameter aus effects extrahieren (alte Struktur)
if (n.effects && Array.isArray(n.effects)) {
for (const effect of n.effects) {
if (effect.type === 'money_change') {
@@ -270,7 +366,6 @@ export default {
}
} else if (effect.type === 'character_health_change') {
if (effect.character_id) {
// Prefer explicit characterName from notification, otherwise fall back to provided name or use id placeholder
params.character_id = effect.character_id;
params.characterName = params.characterName || n.characterName || `#${effect.character_id}`;
}
@@ -304,7 +399,54 @@ export default {
params.amount = n.amount;
}
return this.formatParams(params);
return params;
},
formatDescriptionWithEffects(baseDescription, effects, params) {
if (!effects || !Array.isArray(effects) || effects.length === 0) {
return baseDescription;
}
let description = baseDescription;
const effectDetails = [];
for (const effect of effects) {
if (effect.type === 'character_health_change') {
const charName = effect.character_first_name && effect.character_last_name
? `${effect.character_first_name} ${effect.character_last_name}`.trim()
: params.characterName || `#${effect.character_id}`;
const change = effect.change > 0 ? `+${effect.change}` : `${effect.change}`;
effectDetails.push(`${charName} hat ${change} Gesundheit verloren.`);
} else if (effect.type === 'character_death') {
const charName = effect.character_first_name && effect.character_last_name
? `${effect.character_first_name} ${effect.character_last_name}`.trim()
: params.characterName || `#${effect.character_id}`;
effectDetails.push(`${charName} ist verstorben.`);
} else if (effect.type === 'production_quality_change') {
const change = effect.change > 0 ? `+${effect.change}` : `${effect.change}`;
effectDetails.push(`Produktionsqualität: ${change}.`);
} else if (effect.type === 'transport_speed_change') {
const percent = effect.percent > 0 ? `+${effect.percent.toFixed(1)}%` : `${effect.percent.toFixed(1)}%`;
effectDetails.push(`Transportgeschwindigkeit: ${percent}.`);
} else if (effect.type === 'storage_damage') {
const stockType = effect.stock_type || 'Lager';
const inventoryDamage = effect.inventory_damage_percent ? `${effect.inventory_damage_percent.toFixed(1)}%` : '0%';
const storageDestruction = effect.storage_destruction_percent ? `${effect.storage_destruction_percent.toFixed(1)}%` : '0%';
const affected = effect.affected_stocks ? effect.affected_stocks.length : 0;
const destroyed = effect.destroyed_stocks ? effect.destroyed_stocks.length : 0;
effectDetails.push(`${stockType}: ${inventoryDamage} Lagerbestand beschädigt, ${storageDestruction} Lager zerstört (${affected} betroffen, ${destroyed} zerstört).`);
} else if (effect.type === 'storage_capacity_change') {
const percent = effect.percent > 0 ? `+${effect.percent.toFixed(1)}%` : `${effect.percent.toFixed(1)}%`;
const affected = effect.affected_stocks ? effect.affected_stocks.length : 0;
effectDetails.push(`Lagerkapazität: ${percent} (${affected} betroffen).`);
}
}
if (effectDetails.length > 0) {
description += ' ' + effectDetails.join(' ');
}
return description;
},
interpolateString(str, params) {