feat: add Equipment tab with slot-type validation
Add a new Equipment tab to manage player gear and stat bonuses. Features: - 19 equipment slots across 8 categories (helmet, necklace, body armor, gloves, pants, shoes, rings, accessories) - Type-to-slot validation: each type has max equipped limits (1 helmet, 10 rings, 3 accessories, etc.) - Auto-slot assignment: equipping a ring fills the first available ring slot - Stat bonuses from equipped items display on RPG attributes (e.g. STR 10 +2) - Create/edit modal with stat checkboxes per RPG attribute - Inventory list for unequipped items Architecture: - Shared constants in src/systems/equipment/constants.js - Category-based types (Ring, Accessory) with auto-slot assignment - v7 migration converts legacy slot-specific types to generic categories - Full i18n support for all UI strings Files: - New: src/systems/equipment/constants.js - New: src/systems/interaction/equipmentActions.js - New: src/systems/rendering/equipment.js - Modified: state.js, persistence.js, template.html, index.js - Modified: userStats.js, desktop.js, mobile.js, layout.js, modals.js - Modified: apiClient.js, sillytavern.js, style.css, en.json
This commit is contained in:
1 parent
38fb3d8c51
commit
10cfe581ac
16 files changed
+1428
-17
No files matched your search
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
* Equipment Actions Module
|
||||
* Handles all user interactions with the equipment system
|
||||
*/
|
||||
|
||||
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
|
||||
import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js';
|
||||
import { renderEquipment } from '../rendering/equipment.js';
|
||||
import { renderUserStats } from '../rendering/userStats.js';
|
||||
import { EQUIPMENT_CATEGORIES, escapeHtml } from '../equipment/constants.js';
|
||||
import { i18n } from '../../core/i18n.js';
|
||||
|
||||
/**
|
||||
* Check if a given slot is currently occupied by any item
|
||||
* @param {string} slotId - Slot to check
|
||||
* @param {Array} items - Equipment items array
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSlotOccupied(slotId, items) {
|
||||
return items.some(item => item.slot === slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first available slot for a given equipment type
|
||||
* @param {string} type - Equipment type (helmet, ring, accessory, etc.)
|
||||
* @param {Array} items - Equipment items array
|
||||
* @returns {string|null} Available slot ID or null if all full
|
||||
*/
|
||||
function findAvailableSlot(type, items) {
|
||||
const category = EQUIPMENT_CATEGORIES[type];
|
||||
if (!category) return null;
|
||||
return category.slots.find(slot => !isSlotOccupied(slot, items)) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the slot ID currently assigned to an item
|
||||
* @param {Object} item - Equipment item
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function getItemSlot(item) {
|
||||
return item.slot || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert old specific slot type (e.g. 'ring3') to generic category (e.g. 'ring')
|
||||
* @param {string} type - Equipment type
|
||||
* @returns {string} Generic type
|
||||
*/
|
||||
function normalizeType(type) {
|
||||
if (!type) return type;
|
||||
if (type.startsWith('ring') && type !== 'ring') return 'ring';
|
||||
if (type.startsWith('accessory') && type !== 'accessory') return 'accessory';
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate old item types in the entire items array (for v6 → v7 migration)
|
||||
* @param {Array} items - Equipment items array
|
||||
*/
|
||||
function migrateItemTypes(items) {
|
||||
for (const item of items) {
|
||||
if (item.type) {
|
||||
item.type = normalizeType(item.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique ID for equipment items
|
||||
* @returns {string} Unique ID
|
||||
*/
|
||||
function generateItemId() {
|
||||
return 'eq_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates lastGeneratedData and committedTrackerData to include current equipment state
|
||||
*/
|
||||
function updateEquipmentData() {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
const currentData = lastGeneratedData.userStats || committedTrackerData.userStats;
|
||||
|
||||
if (currentData) {
|
||||
const trimmed = currentData.trim();
|
||||
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||||
try {
|
||||
const jsonData = JSON.parse(currentData);
|
||||
if (jsonData && typeof jsonData === 'object') {
|
||||
jsonData.equipment = JSON.parse(JSON.stringify(equipment));
|
||||
const updatedJSON = JSON.stringify(jsonData, null, 2);
|
||||
lastGeneratedData.userStats = updatedJSON;
|
||||
committedTrackerData.userStats = updatedJSON;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[RPG Equipment] Failed to parse JSON, falling back to text format:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: rebuild text format
|
||||
const stats = extensionSettings.userStats;
|
||||
const config = extensionSettings.trackerConfig?.userStats || {};
|
||||
let text = '';
|
||||
|
||||
const enabledStats = config.customStats?.filter(stat => stat && stat.enabled && stat.name && stat.id) || [];
|
||||
for (const stat of enabledStats) {
|
||||
const value = stats[stat.id] !== undefined ? stats[stat.id] : 100;
|
||||
text += `${stat.name}: ${value}%\n`;
|
||||
}
|
||||
|
||||
if (config.statusSection?.enabled) {
|
||||
if (config.statusSection.showMoodEmoji) {
|
||||
text += `${stats.mood}: `;
|
||||
}
|
||||
text += `${stats.conditions || 'None'}\n`;
|
||||
}
|
||||
|
||||
// Include equipment data in fallback
|
||||
const equipped = equipment.items.filter(item => item.slot);
|
||||
if (equipped.length > 0) {
|
||||
text += `\nEquipment:\n`;
|
||||
for (const item of equipped) {
|
||||
const bonuses = Object.entries(item.stats || {})
|
||||
.filter(([_, val]) => val > 0)
|
||||
.map(([key, val]) => `${key.toUpperCase()}+${val}`)
|
||||
.join(' ');
|
||||
text += `- ${item.slot}: ${item.name}${bonuses ? ' (' + bonuses + ')' : ''}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
lastGeneratedData.userStats = text.trim();
|
||||
committedTrackerData.userStats = text.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the equipment creation modal
|
||||
*/
|
||||
export function showCreateModal() {
|
||||
const modalHtml = generateCreateModalHTML();
|
||||
$('body').append(modalHtml);
|
||||
|
||||
const $modal = $('#rpg-equipment-modal');
|
||||
$modal.hide().fadeIn(200);
|
||||
|
||||
$('#rpg-eq-name').focus();
|
||||
|
||||
initStatCheckboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the equipment edit modal
|
||||
* @param {string} itemId - ID of the item to edit
|
||||
*/
|
||||
export function showEditModal(itemId) {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
const item = equipment.items.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
const modalHtml = generateEditModalHTML(item);
|
||||
$('body').append(modalHtml);
|
||||
|
||||
const $modal = $('#rpg-equipment-modal');
|
||||
$modal.hide().fadeIn(200);
|
||||
|
||||
initStatCheckboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the equipment modal
|
||||
*/
|
||||
export function closeModal() {
|
||||
$('#rpg-equipment-modal').fadeOut(150, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the create modal
|
||||
* @returns {string} Modal HTML
|
||||
*/
|
||||
function generateCreateModalHTML() {
|
||||
const attributes = getAvailableAttributes();
|
||||
const typeOptions = generateTypeOptions(null);
|
||||
const statCheckboxes = generateStatCheckboxes(attributes, null);
|
||||
|
||||
return `
|
||||
<div id="rpg-equipment-modal" class="rpg-equipment-modal" role="dialog" aria-modal="true">
|
||||
<div class="rpg-equipment-modal-content">
|
||||
<header class="rpg-equipment-modal-header">
|
||||
<h3>
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
<span>${i18n.getTranslation('equipment.createItemTitle') || 'Create Equipment'}</span>
|
||||
</h3>
|
||||
<button id="rpg-equipment-modal-close" class="rpg-popup-close" type="button">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
</button>
|
||||
</header>
|
||||
<div class="rpg-equipment-modal-body">
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-name">${i18n.getTranslation('equipment.name') || 'Name'}:</label>
|
||||
<input type="text" id="rpg-eq-name" class="rpg-input" maxlength="50" placeholder="${i18n.getTranslation('equipment.namePlaceholder') || 'Enter equipment name...'}" />
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-type">${i18n.getTranslation('equipment.type') || 'Type'}:</label>
|
||||
<select id="rpg-eq-type" class="rpg-select">${typeOptions}</select>
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label>${i18n.getTranslation('equipment.stats') || 'Stats'}:</label>
|
||||
<div class="rpg-eq-stats-grid">${statCheckboxes}</div>
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-description">${i18n.getTranslation('equipment.description') || 'Description'}:</label>
|
||||
<textarea id="rpg-eq-description" class="rpg-textarea" maxlength="200" rows="2" placeholder="${i18n.getTranslation('equipment.descriptionPlaceholder') || 'Enter description (optional)...'}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="rpg-equipment-modal-footer">
|
||||
<button id="rpg-equipment-modal-cancel" class="rpg-btn-secondary" type="button">
|
||||
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
|
||||
</button>
|
||||
<button id="rpg-equipment-modal-save" class="rpg-btn-primary" type="button">
|
||||
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.save') || 'Save'}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the edit modal
|
||||
* @param {Object} item - The item to edit
|
||||
* @returns {string} Modal HTML
|
||||
*/
|
||||
function generateEditModalHTML(item) {
|
||||
const attributes = getAvailableAttributes();
|
||||
const typeOptions = generateTypeOptions(item.type);
|
||||
const statCheckboxes = generateStatCheckboxes(attributes, item);
|
||||
|
||||
return `
|
||||
<div id="rpg-equipment-modal" class="rpg-equipment-modal" role="dialog" aria-modal="true">
|
||||
<div class="rpg-equipment-modal-content">
|
||||
<header class="rpg-equipment-modal-header">
|
||||
<h3>
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
<span>${i18n.getTranslation('equipment.editItemTitle') || 'Edit Equipment'}</span>
|
||||
</h3>
|
||||
<button id="rpg-equipment-modal-close" class="rpg-popup-close" type="button">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
</button>
|
||||
</header>
|
||||
<div class="rpg-equipment-modal-body">
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-name">${i18n.getTranslation('equipment.name') || 'Name'}:</label>
|
||||
<input type="text" id="rpg-eq-name" class="rpg-input" maxlength="50" value="${escapeHtml(item.name)}" placeholder="${i18n.getTranslation('equipment.namePlaceholder') || 'Enter equipment name...'}" />
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-type">${i18n.getTranslation('equipment.type') || 'Type'}:</label>
|
||||
<select id="rpg-eq-type" class="rpg-select">${typeOptions}</select>
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label>${i18n.getTranslation('equipment.stats') || 'Stats'}:</label>
|
||||
<div class="rpg-eq-stats-grid">${statCheckboxes}</div>
|
||||
</div>
|
||||
<div class="rpg-equipment-form-group">
|
||||
<label for="rpg-eq-description">${i18n.getTranslation('equipment.description') || 'Description'}:</label>
|
||||
<textarea id="rpg-eq-description" class="rpg-textarea" maxlength="200" rows="2" placeholder="${i18n.getTranslation('equipment.descriptionPlaceholder') || 'Enter description (optional)...'}">${escapeHtml(item.description || '')}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="rpg-equipment-modal-footer">
|
||||
<button id="rpg-equipment-modal-cancel" class="rpg-btn-secondary" type="button">
|
||||
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
|
||||
</button>
|
||||
<button id="rpg-equipment-modal-save" class="rpg-btn-primary" type="button" data-edit-id="${item.id}">
|
||||
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.save') || 'Save'}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates type dropdown options
|
||||
* @param {string|null} selectedType - Currently selected type or null
|
||||
* @returns {string} HTML options
|
||||
*/
|
||||
function generateTypeOptions(selectedType) {
|
||||
const types = Object.entries(EQUIPMENT_CATEGORIES).map(([value, def]) => ({
|
||||
value,
|
||||
label: def.slots[0] === value
|
||||
? def.slots[0].charAt(0).toUpperCase() + def.slots[0].slice(1)
|
||||
: value.charAt(0).toUpperCase() + value.slice(1) + (def.slots.length > 1 ? ` (max ${def.maxEquipped})` : '')
|
||||
}));
|
||||
|
||||
return types.map(type => {
|
||||
const selected = selectedType === type.value ? 'selected' : '';
|
||||
return `<option value="${type.value}" ${selected}>${type.label}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates stat checkboxes HTML
|
||||
* @param {string[]} attributes - Available attribute IDs
|
||||
* @param {Object|null} item - Current item for editing or null
|
||||
* @returns {string} HTML for stat checkboxes
|
||||
*/
|
||||
function generateStatCheckboxes(attributes, item) {
|
||||
return attributes.map(attr => {
|
||||
const checked = item && item.stats && item.stats[attr] > 0;
|
||||
const val = checked ? item.stats[attr] : 1;
|
||||
return `
|
||||
<label class="rpg-eq-stat-checkbox">
|
||||
<input type="checkbox" class="rpg-eq-stat-check" data-attr="${attr}" ${checked ? 'checked' : ''} />
|
||||
<span class="rpg-eq-stat-check-label">${attr.toUpperCase()}</span>
|
||||
<input type="number" class="rpg-eq-stat-value-input" data-attr="${attr}" value="${val}" min="1" max="20" ${!checked ? 'disabled' : ''} />
|
||||
</label>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets available RPG attribute IDs from config
|
||||
* @returns {string[]} Array of attribute IDs
|
||||
*/
|
||||
function getAvailableAttributes() {
|
||||
const config = extensionSettings.trackerConfig?.userStats || {};
|
||||
const rpgAttributes = config.rpgAttributes || [];
|
||||
return rpgAttributes
|
||||
.filter(attr => attr && attr.enabled && attr.id)
|
||||
.map(attr => attr.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes stat checkbox change handlers
|
||||
*/
|
||||
function initStatCheckboxes() {
|
||||
$('.rpg-eq-stat-check').on('change', function() {
|
||||
const $input = $(this).siblings('.rpg-eq-stat-value-input');
|
||||
$input.prop('disabled', !$(this).prop('checked'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the equipment item from the modal form
|
||||
*/
|
||||
export function saveEquipmentItem() {
|
||||
const name = $('#rpg-eq-name').val().trim();
|
||||
const type = $('#rpg-eq-type').val();
|
||||
const description = $('#rpg-eq-description').val().trim();
|
||||
const editId = $('#rpg-equipment-modal-save').data('edit-id');
|
||||
|
||||
if (!name) {
|
||||
$('#rpg-eq-name').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect stats
|
||||
const stats = {};
|
||||
$('.rpg-eq-stat-check:checked').each(function() {
|
||||
const attr = $(this).data('attr');
|
||||
const val = parseInt($(this).siblings('.rpg-eq-stat-value-input').val()) || 1;
|
||||
stats[attr] = Math.max(1, Math.min(20, val));
|
||||
});
|
||||
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
|
||||
// Migrate old types (ring1-ring10 -> ring, accessory1-3 -> accessory)
|
||||
migrateItemTypes(equipment.items);
|
||||
|
||||
if (editId) {
|
||||
// Edit existing item
|
||||
const item = equipment.items.find(i => i.id === editId);
|
||||
if (item) {
|
||||
const wasEquipped = !!item.slot;
|
||||
item.name = name;
|
||||
item.type = normalizeType(type);
|
||||
item.stats = stats;
|
||||
item.description = description;
|
||||
|
||||
// If type changed and item was equipped, re-equip to valid slot for new type
|
||||
if (wasEquipped) {
|
||||
const newCategory = EQUIPMENT_CATEGORIES[item.type];
|
||||
if (newCategory && !newCategory.slots.includes(item.slot)) {
|
||||
// Old slot is invalid for new type, find a new one
|
||||
const newSlot = findAvailableSlot(item.type, equipment.items.filter(i => i.id !== editId));
|
||||
item.slot = newSlot || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create new item
|
||||
const newItem = {
|
||||
id: generateItemId(),
|
||||
name: name,
|
||||
type: normalizeType(type),
|
||||
stats: stats,
|
||||
description: description,
|
||||
slot: null
|
||||
};
|
||||
equipment.items.push(newItem);
|
||||
|
||||
// Auto-equip to first available slot
|
||||
const availableSlot = findAvailableSlot(newItem.type, equipment.items);
|
||||
if (availableSlot) {
|
||||
newItem.slot = availableSlot;
|
||||
} else {
|
||||
console.warn(`[RPG Equipment] Created "${name}" but no available slot for type "${newItem.type}". Item added to inventory unequipped.`);
|
||||
}
|
||||
}
|
||||
|
||||
updateEquipmentData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
closeModal();
|
||||
renderEquipment();
|
||||
renderUserStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equips an item to its designated slot
|
||||
* @param {string} itemId - ID of the item to equip
|
||||
*/
|
||||
export function equipItem(itemId) {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
const item = equipment.items.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
const type = normalizeType(item.type);
|
||||
item.type = type;
|
||||
|
||||
const category = EQUIPMENT_CATEGORIES[type];
|
||||
if (!category) return;
|
||||
|
||||
const availableSlot = findAvailableSlot(type, equipment.items);
|
||||
if (!availableSlot) {
|
||||
console.warn(`[RPG Equipment] No available slot for type "${type}". All ${EQUIPMENT_CATEGORIES[type].maxEquipped} slots are full.`);
|
||||
return;
|
||||
}
|
||||
|
||||
item.slot = availableSlot;
|
||||
|
||||
updateEquipmentData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
renderEquipment();
|
||||
renderUserStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unequips an item from a slot
|
||||
* @param {string} slotId - The slot to unequip from
|
||||
*/
|
||||
export function unequipItem(slotId) {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
const item = equipment.items.find(i => i.slot === slotId);
|
||||
if (item) {
|
||||
item.slot = null;
|
||||
}
|
||||
|
||||
updateEquipmentData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
renderEquipment();
|
||||
renderUserStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an equipment item
|
||||
* @param {string} itemId - ID of the item to delete
|
||||
*/
|
||||
export function deleteItem(itemId) {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
|
||||
// Unequip if currently equipped
|
||||
const item = equipment.items.find(i => i.id === itemId);
|
||||
if (item && item.slot) {
|
||||
item.slot = null;
|
||||
}
|
||||
|
||||
// Remove from items array
|
||||
equipment.items = equipment.items.filter(i => i.id !== itemId);
|
||||
|
||||
updateEquipmentData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
renderEquipment();
|
||||
renderUserStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total equipment bonuses per attribute from currently equipped items
|
||||
* @returns {Object} Map of attribute ID to total bonus value
|
||||
*/
|
||||
export function getEquipmentBonuses() {
|
||||
const equipment = extensionSettings.userStats.equipment;
|
||||
const bonuses = {};
|
||||
const items = equipment.items || [];
|
||||
|
||||
for (const item of items) {
|
||||
if (!item.slot || !item.stats) continue;
|
||||
|
||||
for (const [attr, val] of Object.entries(item.stats)) {
|
||||
if (val > 0) {
|
||||
bonuses[attr] = (bonuses[attr] || 0) + val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all event listeners for equipment interactions
|
||||
*/
|
||||
export function initEquipmentEventListeners() {
|
||||
// Show create modal
|
||||
$(document).on('click', '[data-action="show-create-modal"]', function(e) {
|
||||
e.preventDefault();
|
||||
showCreateModal();
|
||||
});
|
||||
|
||||
// Equip item from inventory
|
||||
$(document).on('click', '[data-action="equip"]', function(e) {
|
||||
e.preventDefault();
|
||||
const itemId = $(this).data('item-id');
|
||||
equipItem(itemId);
|
||||
});
|
||||
|
||||
// Unequip item from slot
|
||||
$(document).on('click', '[data-action="unequip"]', function(e) {
|
||||
e.preventDefault();
|
||||
const slot = $(this).closest('.rpg-equipment-slot').data('slot');
|
||||
unequipItem(slot);
|
||||
});
|
||||
|
||||
// Edit item
|
||||
$(document).on('click', '[data-action="edit-item"]', function(e) {
|
||||
e.preventDefault();
|
||||
const itemId = $(this).data('item-id');
|
||||
showEditModal(itemId);
|
||||
});
|
||||
|
||||
// Delete item
|
||||
$(document).on('click', '[data-action="delete-item"]', function(e) {
|
||||
e.preventDefault();
|
||||
const itemId = $(this).data('item-id');
|
||||
deleteItem(itemId);
|
||||
});
|
||||
|
||||
// Modal close button
|
||||
$(document).on('click', '#rpg-equipment-modal-close', closeModal);
|
||||
|
||||
// Modal cancel button
|
||||
$(document).on('click', '#rpg-equipment-modal-cancel', closeModal);
|
||||
|
||||
// Modal save button
|
||||
$(document).on('click', '#rpg-equipment-modal-save', saveEquipmentItem);
|
||||
|
||||
// Close modal on backdrop click
|
||||
$(document).on('click', '#rpg-equipment-modal', function(e) {
|
||||
if (e.target === this) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Enter key to save in modal inputs
|
||||
$(document).on('keypress', '#rpg-eq-name', function(e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
saveEquipmentItem();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user