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 }"> <li v-for="n in messages" :key="n.id" :class="{ unread: !n.shown }">
<div class="body"> <div class="body">
<div v-if="formatBody(n).title" class="notification-title">{{ formatBody(n).title }}</div> <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>
<div class="footer"> <div class="footer">
<span>{{ formatDate(n.createdAt) }}</span> <span>{{ formatDate(n.createdAt) }}</span>
@@ -133,54 +133,52 @@ export default {
} catch { return dt; } } catch { return dt; }
}, },
formatBody(n) { 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 raw = n.tr || '';
let parsed = null;
let value = null;
let key = raw; let key = raw;
let params = {}; 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') { if (typeof raw === 'string') {
const trimmed = raw.trim(); const trimmed = raw.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) { if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
try { try {
const parsed = JSON.parse(trimmed); parsed = JSON.parse(trimmed);
if (parsed && parsed.tr) { if (parsed && parsed.tr) {
raw = parsed.tr;
key = parsed.tr; key = parsed.tr;
// Alle anderen Felder als Parameter verwenden und formatieren value = parsed.value || {};
params = this.formatParams({ ...parsed });
delete params.tr; // Extrahiere Parameter aus value und effects
// Merge in params extracted from nested structures (effects, character ids) params = this.extractParamsFromValue(value, n);
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
}
} }
} catch (e) { } 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: // 2) Wenn value.title und value.description vorhanden sind, verwende diese
// - wenn bereits ein voller i18n-Key wie "falukant.notifications.production.overproduction", if (value && value.title && value.description) {
// dann direkt verwenden // Parameter aus effects extrahieren und formatieren
// - sonst in den Namespace "falukant.notifications." hängen 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') { if (typeof key === 'string') {
const trimmedKey = key.trim(); const trimmedKey = key.trim();
if (trimmedKey.startsWith('falukant.')) { 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.')) { if (key.startsWith('falukant.notifications.random_event.')) {
const eventId = key.replace('falukant.notifications.random_event.', ''); const eventId = key.replace('falukant.notifications.random_event.', '');
const eventKey = `falukant.notifications.random_event.${eventId}`; const eventKey = `falukant.notifications.random_event.${eventId}`;
try { try {
const titleKey = `${eventKey}.title`; const titleKey = `${eventKey}.title`;
const descKey = `${eventKey}.description`; 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) { if ((!params || Object.keys(params).length === 0) && n) {
params = this.extractParams(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)) { if (this.$te(titleKey) && this.$te(descKey)) {
const title = this.$t(titleKey, params); const title = this.$t(titleKey, formattedParams);
const description = this.$t(descKey, params); 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 }; return { title, description };
} }
} catch (e) { } catch (e) {
@@ -213,42 +225,64 @@ export default {
} }
// Fallback: Alte Methode für andere Notification-Typen // 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) { formatParams(params) {
const formatted = {}; const formatted = {};
// Geldbeträge formatieren // Geldbeträge formatieren
if (params.amount !== undefined) { if (params.amount !== undefined && params.amount !== null) {
formatted.amount = this.formatMoney(params.amount); formatted.amount = this.formatMoney(params.amount);
} }
if (params.absolute !== undefined) { if (params.absolute !== undefined && params.absolute !== null) {
formatted.amount = this.formatMoney(params.absolute); formatted.amount = this.formatMoney(params.absolute);
} }
if (params.percent !== undefined) { if (params.percent !== undefined && params.percent !== null) {
formatted.amount = `${params.percent > 0 ? '+' : ''}${params.percent.toFixed(1)}%`; formatted.percent = `${params.percent > 0 ? '+' : ''}${params.percent.toFixed(1)}%`;
} }
// Gesundheit formatieren // Gesundheit formatieren
if (params.change !== undefined) { if (params.change !== undefined && params.change !== null) {
formatted.healthChange = params.change > 0 ? `+${params.change}` : `${params.change}`; formatted.healthChange = params.change > 0 ? `+${params.change}` : `${params.change}`;
formatted.change = formatted.healthChange;
} }
if (params.healthChange !== undefined) { if (params.healthChange !== undefined && params.healthChange !== null && !formatted.healthChange) {
formatted.healthChange = params.healthChange > 0 ? `+${params.healthChange}` : `${params.healthChange}`; formatted.healthChange = typeof params.healthChange === 'string'
? params.healthChange
: (params.healthChange > 0 ? `+${params.healthChange}` : `${params.healthChange}`);
} }
// Schaden formatieren // Charakternamen
if (params.inventory_damage_percent !== undefined) { 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)}%.`; 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)}%.`; formatted.destructionPercent = ` Lager zerstört: ${params.storage_destruction_percent.toFixed(1)}%.`;
} }
// Alle anderen Parameter übernehmen // Alle anderen Parameter übernehmen
for (const [key, value] of Object.entries(params)) { 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; formatted[key] = value;
} }
} }
@@ -256,10 +290,72 @@ export default {
return formatted; 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) { extractParams(n) {
const params = {}; const params = {};
// Parameter aus effects extrahieren // Parameter aus effects extrahieren (alte Struktur)
if (n.effects && Array.isArray(n.effects)) { if (n.effects && Array.isArray(n.effects)) {
for (const effect of n.effects) { for (const effect of n.effects) {
if (effect.type === 'money_change') { if (effect.type === 'money_change') {
@@ -270,7 +366,6 @@ export default {
} }
} else if (effect.type === 'character_health_change') { } else if (effect.type === 'character_health_change') {
if (effect.character_id) { 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.character_id = effect.character_id;
params.characterName = params.characterName || n.characterName || `#${effect.character_id}`; params.characterName = params.characterName || n.characterName || `#${effect.character_id}`;
} }
@@ -304,7 +399,54 @@ export default {
params.amount = n.amount; 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) { interpolateString(str, params) {