From 60e4a6c2cc6b75664a153498a181e765e0d28b7a Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Fri, 17 Oct 2025 15:28:59 +1100 Subject: [PATCH] feat(inventory): add interaction handlers for v2 system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/systems/interaction/inventoryActions.js | 297 ++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 src/systems/interaction/inventoryActions.js diff --git a/src/systems/interaction/inventoryActions.js b/src/systems/interaction/inventoryActions.js new file mode 100644 index 0000000..1a84452 --- /dev/null +++ b/src/systems/interaction/inventoryActions.js @@ -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} + */ +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} + */ +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} + */ +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} + */ +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} + */ +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 + }; +}