Files
rpg-companion-sillytavern/src/systems/rendering/equipment.js
T

158 lines
7.1 KiB
JavaScript

/**
* Equipment Rendering Module
* Handles UI rendering for the equipment grid and item creation
*/
import { extensionSettings, $equipmentContainer } from '../../core/state.js';
import { i18n } from '../../core/i18n.js';
import { EQUIPMENT_CATEGORIES, SLOTS_LIST, escapeHtml } from '../equipment/constants.js';
/**
* Renders a single equipment slot
* @param {Object} slotDef - Slot definition from SLOTS_LIST
* @param {Object|null} item - The equipped item or null
* @returns {string} HTML for the slot
*/
function renderSlot(slotDef, item) {
const slotId = slotDef.id;
const slotName = i18n.getTranslation(`equipment.slots.${slotId}`) || slotId.replace(/(\d+)/, ' $1');
const equippedClass = item ? 'equipped' : '';
if (item) {
const statsText = Object.entries(item.stats || {})
.filter(([_, val]) => val > 0)
.map(([key, val]) => `<span class="rpg-eq-stat"><span class="rpg-eq-stat-label">${escapeHtml(key.toUpperCase())}</span><span class="rpg-eq-stat-value">+${val}</span></span>`)
.join('');
return `
<div class="rpg-equipment-slot ${equippedClass}" data-slot="${slotId}" data-item-id="${item.id}">
<div class="rpg-equipment-slot-header">
<i class="fa-solid ${slotDef.icon}"></i>
<span class="rpg-equipment-slot-name">${escapeHtml(slotName)}</span>
<button class="rpg-equipment-unequip-btn" data-action="unequip" title="${i18n.getTranslation('equipment.unequip') || 'Unequip'}">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="rpg-equipment-item-name">${escapeHtml(item.name)}</div>
${statsText ? `<div class="rpg-equipment-stats">${statsText}</div>` : ''}
${item.description ? `<div class="rpg-equipment-description">${escapeHtml(item.description)}</div>` : ''}
<div class="rpg-equipment-item-actions">
<button class="rpg-equipment-edit-btn" data-action="edit-item" data-item-id="${item.id}" title="${i18n.getTranslation('equipment.editItem') || 'Edit item'}">
<i class="fa-solid fa-pen"></i>
</button>
<button class="rpg-equipment-delete-btn" data-action="delete-item" data-item-id="${item.id}" title="${i18n.getTranslation('equipment.deleteItem') || 'Delete item'}">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
`;
}
return `
<div class="rpg-equipment-slot" data-slot="${slotId}">
<div class="rpg-equipment-slot-header">
<i class="fa-solid ${slotDef.icon}"></i>
<span class="rpg-equipment-slot-name">${escapeHtml(slotName)}</span>
</div>
<div class="rpg-equipment-empty">${i18n.getTranslation('equipment.emptySlot') || 'Empty'}</div>
</div>
`;
}
/**
* Generates the full equipment section HTML
* @returns {string} Complete HTML for the equipment section
*/
function generateEquipmentHTML() {
const equipment = extensionSettings.userStats?.equipment || { items: [], slots: {} };
const slots = equipment.slots || {};
const items = equipment.items || [];
let html = '<div class="rpg-equipment-container">';
// Header with add button
html += `
<div class="rpg-equipment-header">
<h3>
<i class="fa-solid fa-shield-halved"></i>
<span data-i18n-key="equipment.title">${i18n.getTranslation('equipment.title') || 'Equipment'}</span>
</h3>
<button class="rpg-equipment-add-btn" data-action="show-create-modal" title="${i18n.getTranslation('equipment.createItem') || 'Create new equipment'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('equipment.createItem') || 'Create Equipment'}
</button>
</div>
`;
// Equipment grid
html += '<div class="rpg-equipment-grid">';
// Render each slot
for (const slotDef of SLOTS_LIST) {
const item = items.find(i => i.slot === slotDef.id);
html += renderSlot(slotDef, item);
}
html += '</div>';
// Inventory list (items not currently equipped)
const unequipped = items.filter(item => !item.slot);
if (unequipped.length > 0) {
html += '<div class="rpg-equipment-inventory">';
html += `<h4>${i18n.getTranslation('equipment.inventoryTitle') || 'Inventory'}</h4>`;
html += '<div class="rpg-equipment-inventory-list">';
for (const item of unequipped) {
const category = EQUIPMENT_CATEGORIES[item.type];
const statsText = Object.entries(item.stats || {})
.filter(([_, val]) => val > 0)
.map(([key, val]) => `<span class="rpg-eq-stat"><span class="rpg-eq-stat-label">${escapeHtml(key.toUpperCase())}</span><span class="rpg-eq-stat-value">+${val}</span></span>`)
.join('');
html += `
<div class="rpg-equipment-inventory-item" data-item-id="${item.id}">
<div class="rpg-equipment-inventory-item-header">
<i class="fa-solid ${category ? category.icon : 'fa-circle'}"></i>
<span class="rpg-equipment-inventory-item-name">${escapeHtml(item.name)}</span>
<span class="rpg-equipment-inventory-item-type">${category ? (i18n.getTranslation(`equipment.types.${item.type}`) || item.type) : escapeHtml(item.type)}</span>
</div>
${statsText ? `<div class="rpg-equipment-stats">${statsText}</div>` : ''}
<div class="rpg-equipment-item-actions">
<button class="rpg-equipment-equip-btn" data-action="equip" data-item-id="${item.id}" title="${i18n.getTranslation('equipment.equip') || 'Equip'}">
<i class="fa-solid fa-hand"></i>
</button>
<button class="rpg-equipment-edit-btn" data-action="edit-item" data-item-id="${item.id}" title="${i18n.getTranslation('equipment.editItem') || 'Edit item'}">
<i class="fa-solid fa-pen"></i>
</button>
<button class="rpg-equipment-delete-btn" data-action="delete-item" data-item-id="${item.id}" title="${i18n.getTranslation('equipment.deleteItem') || 'Delete item'}">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
`;
}
html += '</div>';
html += '</div>';
}
html += '</div>';
return html;
}
/**
* Main equipment rendering function
* Gets data from state/settings and updates DOM directly.
*/
export function renderEquipment() {
if (!$equipmentContainer || !extensionSettings.showEquipment) {
return;
}
const html = generateEquipmentHTML();
$equipmentContainer.html(html);
// Re-apply translations
i18n.applyTranslations($equipmentContainer[0]);
}