Calculator:HighLite/Nameplates

Revision as of 19:18, 18 July 2025 by Ryan (talk | contribs)

HighLite Nameplate Calculator

`; // Assign elements to global variables AFTER they are injected itemSearchInput = document.getElementById('item-search'); outputListTextarea = document.getElementById('output-list'); copyButton = document.getElementById('copy-button'); copyFeedback = document.getElementById('copy-feedback'); itemDisplayTable = document.getElementById('item-display-table'); // Get reference to the table on the main page } /** * Initializes the interactive buttons and state for each item in the table. */ function initializeItemRows() { const loadingIndicator = document.getElementById('loading-indicator'); const errorMessage = document.getElementById('error-message'); const calculatorControls = document.getElementById('calculator-controls'); if (!itemDisplayTable || !itemDisplayTable.tBodies[0] || itemDisplayTable.tBodies[0].rows.length === 0) { errorMessage.textContent = 'Error: Item data table not found or empty. Please ensure your Cargo query is correct and populating the table correctly.'; errorMessage.style.display = 'block'; if (loadingIndicator) loadingIndicator.style.display = 'none'; console.error('Item display table (ID: item-display-table) not found or has no tbody/rows after waiting.'); return; } // Store the tbody element once it's confirmed to exist tableTbodyElement = itemDisplayTable.tBodies[0]; const rows = Array.from(tableTbodyElement.rows); // Convert to array for easier manipulation // Temporary map to hold consolidated items and their determined state // Key: consolidated item name, Value: { name, state, rowElement (primary one), buttonsCell (primary one) } const consolidatedItemsMap = new Map(); rows.forEach(row => { const itemNameLinkCell = row.cells[1]; const itemNameLink = itemNameLinkCell ? itemNameLinkCell.querySelector('a') : null; const originalItemName = itemNameLink ? itemNameLink.textContent.trim() : ''; if (!originalItemName) { console.warn('Skipping row due to missing original item name:', row); row.style.display = 'none'; // Hide rows without a name return; } const consolidatedName = getConsolidatedItemName(originalItemName); const buttonsCell = row.querySelector('.nameplate-buttons-cell'); if (!buttonsCell) { console.warn('Skipping row due to missing buttons cell:', row); row.style.display = 'none'; // Hide rows without a buttons cell return; } // Check if we've already processed this consolidated item if (!consolidatedItemsMap.has(consolidatedName)) { // This is the first time we see this consolidated item name consolidatedItemsMap.set(consolidatedName, { name: consolidatedName, state: -1, // Default to hide initially, will be overridden by DEFAULT_ITEM_LIST rowElement: row, // This row will be the primary one for this consolidated item buttonsCell: buttonsCell // This cell will host the buttons }); // Update the displayed item name in the table if (itemNameLink) { itemNameLink.textContent = consolidatedName; } else { // Fallback if no link, though usually there should be one itemNameLinkCell.textContent = consolidatedName; } // Create and append buttons to this consolidated item's primary buttonsCell const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'item-status-buttons'; buttonsContainer.dataset.itemName = consolidatedName; // For event delegation const states = [ { value: -1, text: 'Hide' }, { value: 0, text: 'Default' }, { value: 1, text: 'Priority' } ]; states.forEach(state => { const button = document.createElement('button'); button.textContent = state.text; button.dataset.value = state.value; buttonsContainer.appendChild(button); }); buttonsCell.appendChild(buttonsContainer); } else { // This is a duplicate of a consolidated item, so hide its row row.style.display = 'none'; // And remove any buttons that might have been automatically rendered by Cargo for it if (buttonsCell) { buttonsCell.innerHTML = ''; // Clear contents of the button cell } } }); // Convert the map values to an array for allItems allItems = Array.from(consolidatedItemsMap.values()); // Apply the default list or any saved state to the consolidated items parseAndApplyList(DEFAULT_ITEM_LIST); // This will update the 'state' in allItems generateOutputList(); // Initial generation of the output list if (loadingIndicator) loadingIndicator.style.display = 'none'; if (calculatorControls) calculatorControls.style.display = 'block'; // --- Attach ONE event listener to the tbody for ALL button clicks --- tableTbodyElement.addEventListener('click', (event) => { const clickedButton = event.target.closest('button'); const buttonsContainer = clickedButton ? clickedButton.closest('.item-status-buttons') : null; if (clickedButton && buttonsContainer) { const itemName = buttonsContainer.dataset.itemName; const newState = parseInt(clickedButton.dataset.value, 10); const itemObj = allItems.find(i => i.name === itemName); if (itemObj) { itemObj.state = newState; } // Update active classes for buttons within this specific item's container Array.from(buttonsContainer.children).forEach(btn => { if (parseInt(btn.dataset.value, 10) === newState) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); generateOutputList(); // Auto-update the list } }); } /** * Generates the comma-separated output list. */ function generateOutputList() { const output = allItems .map(item => `${item.name}:${item.state}`) .join(','); outputListTextarea.value = output; } /** * Filters the item list based on search input by hiding/showing table rows. */ function filterItemList() { const searchTerm = itemSearchInput.value.toLowerCase(); allItems.forEach(item => { // We only care about the single primary row for each consolidated item for display const row = item.rowElement; if (row) { if (item.name.toLowerCase().includes(searchTerm)) { row.style.display = ''; // Show the primary row } else { row.style.display = 'none'; // Hide the primary row } } }); } /** * Copies the generated list to the clipboard. */ function copyToClipboard() { outputListTextarea.select(); outputListTextarea.setSelectionRange(0, 99999); try { document.execCommand('copy'); copyFeedback.textContent = 'Copied!'; setTimeout(() => { copyFeedback.textContent = '', 2000; }); } catch (err) { console.error('Failed to copy text: ', err); copyFeedback.textContent = 'Failed to copy.'; setTimeout(() => { copyFeedback.textContent = '', 2000; }); } } /** * Parses a list string and applies the settings to the item buttons. * @param {string} listString The comma-separated list of "ItemName:Value" */ function parseAndApplyList(listString) { const newSettings = new Map(); // Use a Map to store parsed settings and consolidate listString.split(',').forEach(entry => { const parts = entry.trim().split(':'); if (parts.length === 2) { const originalItemName = parts[0].trim(); const value = parseInt(parts[1].trim(), 10); if (!isNaN(value) && [ -1, 0, 1 ].includes(value)) { const consolidatedName = getConsolidatedItemName(originalItemName); const existingValue = newSettings.has(consolidatedName) ? newSettings.get(consolidatedName) : -2; // Use -2 as a placeholder for "not set" // Apply consolidation priority: Priority (1) > Default (0) > Hide (-1) if (value > existingValue) { // Higher value (more priority) wins newSettings.set(consolidatedName, value); } } } }); allItems.forEach(item => { const currentItemName = item.name; // This is already the consolidated name let newState = -1; // Default to Hide if not in the pasted list if (newSettings.has(currentItemName)) { newState = newSettings.get(currentItemName); } // Update the internal state item.state = newState; // Update the active class on the buttons for this item (only the primary one) if (item.buttonsCell) { // Ensure buttonsCell reference exists (this will be the primary one) const buttonsContainer = item.buttonsCell.querySelector('.item-status-buttons'); if (buttonsContainer) { Array.from(buttonsContainer.children).forEach(btn => { if (parseInt(btn.dataset.value, 10) === newState) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); } } }); // Re-generate the output list based on the new states generateOutputList(); } /** * Main entry point for the widget. */ function initWidget() { injectCalculatorControls(); // Inject the HTML for controls first // Now that controls are injected, attach event listeners to them if (itemSearchInput) { itemSearchInput.addEventListener('input', filterItemList); } if (copyButton) { copyButton.addEventListener('click', copyToClipboard); } // Add input listener to the output list textarea for reverse engineering if (outputListTextarea) { outputListTextarea.addEventListener('input', (event) => { parseAndApplyList(event.target.value); }); } // Use a MutationObserver to wait for the Cargo table's tbody to be generated const observer = new MutationObserver((mutations, obs) => { const table = document.getElementById('item-display-table'); // This is the ID of the table on the main page if (table && table.tBodies[0] && table.tBodies[0].rows.length > 0) { obs.disconnect(); // Stop observing once Cargo has populated the table initializeItemRows(); // Proceed with setting up buttons and interactivity } }); // Start observing the body for the main Cargo table to appear and be populated observer.observe(document.body, { childList: true, subtree: true }); // Fallback for immediate execution if DOM is already loaded and table is present if (document.readyState === 'complete') { const table = document.getElementById('item-display-table'); if (table && table.tBodies[0] && table.tBodies[0].rows.length > 0) { observer.disconnect(); initializeItemRows(); } } } // Run initialization when the DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initWidget); } else { initWidget(); } })();