feat(inventory): add interaction handlers for v2 system
Create comprehensive inventory interaction module: **NEW: inventoryActions.js (286 lines)** - editOnPerson() - Edit items currently carried/worn - editStoredLocation() - Edit items at specific storage location - editAssets() - Edit vehicles, property, major possessions - addStorageLocation() - Create new storage location with validation - removeStorageLocation() - Delete location with confirmation - toggleLocationCollapse() - Collapsible sections with persistent state - switchInventoryTab() - Handle sub-tab switching - initInventoryEventListeners() - Event delegation for dynamic content - updateLastGeneratedDataInventory() - Keep AI context synced **Interaction Patterns:** - Uses native prompt() and confirm() for user input - Event delegation: .on() for dynamic elements - Full persistence: saveSettings() + saveChatData() + updateMessageSwipeData() - Auto re-render after every mutation - Maintains UI state (activeSubTab, collapsedLocations) **State Management:** - Tracks collapsed locations across sessions - Persists in extensionSettings.collapsedInventoryLocations - Current tab state maintained in module scope **Data Flow:** User Action → Handler → Update inventory → Update lastGeneratedData → Persist (settings + chat + swipe) → Re-render UI Part of Epic 7.6: Inventory interactions Next: Wire into main panel and initialize event listeners
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Inventory Actions Module
|
||||
* Handles all user interactions with the inventory v2 system
|
||||
*/
|
||||
|
||||
import { extensionSettings, lastGeneratedData } from '../../core/state.js';
|
||||
import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js';
|
||||
import { buildInventorySummary } from '../generation/promptBuilder.js';
|
||||
import { renderInventory, updateInventoryDisplay } from '../rendering/inventory.js';
|
||||
|
||||
// Type imports
|
||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||
|
||||
/**
|
||||
* Current active sub-tab for inventory UI
|
||||
* @type {string}
|
||||
*/
|
||||
let currentActiveSubTab = 'onPerson';
|
||||
|
||||
/**
|
||||
* Array of collapsed storage location names
|
||||
* @type {string[]}
|
||||
*/
|
||||
let collapsedLocations = [];
|
||||
|
||||
/**
|
||||
* Updates lastGeneratedData.userStats to include current inventory in text format.
|
||||
* This ensures the AI context stays synced with manual edits.
|
||||
*/
|
||||
function updateLastGeneratedDataInventory() {
|
||||
const stats = extensionSettings.userStats;
|
||||
const inventorySummary = buildInventorySummary(stats.inventory);
|
||||
|
||||
// Rebuild the lastGeneratedData.userStats text format
|
||||
lastGeneratedData.userStats =
|
||||
`Health: ${stats.health}%\n` +
|
||||
`Satiety: ${stats.satiety}%\n` +
|
||||
`Energy: ${stats.energy}%\n` +
|
||||
`Hygiene: ${stats.hygiene}%\n` +
|
||||
`Arousal: ${stats.arousal}%\n` +
|
||||
`${stats.mood}: ${stats.conditions}\n` +
|
||||
`${inventorySummary}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits items currently on the character's person.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function editOnPerson() {
|
||||
const inventory = extensionSettings.userStats.inventory;
|
||||
const current = inventory.onPerson || 'None';
|
||||
|
||||
const newValue = prompt('Edit items on person (carried/worn):', current);
|
||||
if (newValue === null) return; // User cancelled
|
||||
|
||||
inventory.onPerson = newValue.trim() || 'None';
|
||||
|
||||
updateLastGeneratedDataInventory();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits items stored at a specific location.
|
||||
* @param {string} locationName - Name of the storage location
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function editStoredLocation(locationName) {
|
||||
const inventory = extensionSettings.userStats.inventory;
|
||||
const current = inventory.stored[locationName] || 'None';
|
||||
|
||||
const newValue = prompt(`Edit items stored at "${locationName}":`, current);
|
||||
if (newValue === null) return; // User cancelled
|
||||
|
||||
inventory.stored[locationName] = newValue.trim() || 'None';
|
||||
|
||||
updateLastGeneratedDataInventory();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits character's assets (vehicles, property, major possessions).
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function editAssets() {
|
||||
const inventory = extensionSettings.userStats.inventory;
|
||||
const current = inventory.assets || 'None';
|
||||
|
||||
const newValue = prompt('Edit assets (vehicles, property, equipment):', current);
|
||||
if (newValue === null) return; // User cancelled
|
||||
|
||||
inventory.assets = newValue.trim() || 'None';
|
||||
|
||||
updateLastGeneratedDataInventory();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new storage location to the inventory.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function addStorageLocation() {
|
||||
const inventory = extensionSettings.userStats.inventory;
|
||||
|
||||
const locationName = prompt('Enter name for new storage location:');
|
||||
if (!locationName) return; // User cancelled or entered empty string
|
||||
|
||||
const trimmedName = locationName.trim();
|
||||
|
||||
// Check for duplicate
|
||||
if (inventory.stored[trimmedName]) {
|
||||
alert(`Storage location "${trimmedName}" already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new location with default "None"
|
||||
inventory.stored[trimmedName] = 'None';
|
||||
|
||||
updateLastGeneratedDataInventory();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Switch to stored tab and re-render
|
||||
currentActiveSubTab = 'stored';
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a storage location from the inventory.
|
||||
* @param {string} locationName - Name of location to remove
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function removeStorageLocation(locationName) {
|
||||
const confirmed = confirm(`Remove storage location "${locationName}"?\n\nThis will delete all items stored there.`);
|
||||
if (!confirmed) return;
|
||||
|
||||
const inventory = extensionSettings.userStats.inventory;
|
||||
delete inventory.stored[locationName];
|
||||
|
||||
// Remove from collapsed list if present
|
||||
const index = collapsedLocations.indexOf(locationName);
|
||||
if (index > -1) {
|
||||
collapsedLocations.splice(index, 1);
|
||||
}
|
||||
|
||||
updateLastGeneratedDataInventory();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the collapsed state of a storage location section.
|
||||
* @param {string} locationName - Name of location to toggle
|
||||
*/
|
||||
export function toggleLocationCollapse(locationName) {
|
||||
const index = collapsedLocations.indexOf(locationName);
|
||||
|
||||
if (index > -1) {
|
||||
// Currently collapsed, expand it
|
||||
collapsedLocations.splice(index, 1);
|
||||
} else {
|
||||
// Currently expanded, collapse it
|
||||
collapsedLocations.push(locationName);
|
||||
}
|
||||
|
||||
// Save collapsed state to settings
|
||||
extensionSettings.collapsedInventoryLocations = collapsedLocations;
|
||||
saveSettings();
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the active inventory sub-tab.
|
||||
* @param {string} tabName - Name of the tab ('onPerson', 'stored', 'assets')
|
||||
*/
|
||||
export function switchInventoryTab(tabName) {
|
||||
currentActiveSubTab = tabName;
|
||||
|
||||
// Re-render inventory UI
|
||||
updateInventoryDisplay('rpg-inventory-content', {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all event listeners for inventory interactions.
|
||||
* Uses event delegation to handle dynamically created elements.
|
||||
*/
|
||||
export function initInventoryEventListeners() {
|
||||
// Load collapsed state from settings
|
||||
if (extensionSettings.collapsedInventoryLocations) {
|
||||
collapsedLocations = extensionSettings.collapsedInventoryLocations;
|
||||
}
|
||||
|
||||
// Event delegation for all inventory buttons
|
||||
$(document).on('click', '.rpg-inventory-edit-btn', function(e) {
|
||||
e.preventDefault();
|
||||
const action = $(this).data('action');
|
||||
|
||||
if (action === 'edit-onperson') {
|
||||
editOnPerson();
|
||||
} else if (action === 'edit-location') {
|
||||
const location = $(this).data('location');
|
||||
editStoredLocation(location);
|
||||
} else if (action === 'edit-assets') {
|
||||
editAssets();
|
||||
}
|
||||
});
|
||||
|
||||
// Add location button
|
||||
$(document).on('click', '.rpg-inventory-add-btn', function(e) {
|
||||
e.preventDefault();
|
||||
const action = $(this).data('action');
|
||||
|
||||
if (action === 'add-location') {
|
||||
addStorageLocation();
|
||||
}
|
||||
});
|
||||
|
||||
// Remove location buttons
|
||||
$(document).on('click', '.rpg-inventory-remove-btn', function(e) {
|
||||
e.preventDefault();
|
||||
const action = $(this).data('action');
|
||||
|
||||
if (action === 'remove-location') {
|
||||
const location = $(this).data('location');
|
||||
removeStorageLocation(location);
|
||||
}
|
||||
});
|
||||
|
||||
// Collapse toggle buttons
|
||||
$(document).on('click', '.rpg-storage-toggle', function(e) {
|
||||
e.preventDefault();
|
||||
const location = $(this).data('location');
|
||||
toggleLocationCollapse(location);
|
||||
});
|
||||
|
||||
// Sub-tab switching
|
||||
$(document).on('click', '.rpg-inventory-subtab', function(e) {
|
||||
e.preventDefault();
|
||||
const tab = $(this).data('tab');
|
||||
switchInventoryTab(tab);
|
||||
});
|
||||
|
||||
console.log('[RPG Companion] Inventory event listeners initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current inventory rendering options.
|
||||
* @returns {Object} Options object with activeSubTab and collapsedLocations
|
||||
*/
|
||||
export function getInventoryRenderOptions() {
|
||||
return {
|
||||
activeSubTab: currentActiveSubTab,
|
||||
collapsedLocations
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user