Actions

MediaWiki

MediaWiki:Gadget-ResourcePage.js

From Dune Awakening DB

Revision as of 01:51, 4 June 2025 by Operator (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * ResourcePage Gadget - Complete Fixed Version
 * Handles recipe popups and search functionality for resource pages
 * 
 * @requires jquery
 * @requires mediawiki.api
 * @requires mediawiki.util
 */

(function($, mw) {
    'use strict';
    
    // Only run on pages with resource page elements
    if (!$('.crafting-section, .recipe-table').length) {
        return;
    }
    
    console.log('[ResourcePage] Gadget loading...');
    
    // Cache for storing recipe data
    var recipeCache = {
        craftedWith: null,
        craftedFrom: null
    };
    
    // Initialize module
    function init() {
        console.log('[ResourcePage] Initializing...');
        
        // Add required styles
        injectRequiredStyles();
        
        // Add table search functionality
        initTableSearch();
        
        // Add popup handlers - FIXED VERSION
        initPopupHandlers();
        
        // Format tables
        formatTables();
        
        // Add animation styles
        addAnimationStyles();
    }
    
    // Inject required CSS
    function injectRequiredStyles() {
        if ($('#resourcePageGadgetStyles').length === 0) {
            var styles = `<style id="resourcePageGadgetStyles">
                /* Span button styles */
                .dune-action-button {
                    display: inline-block !important;
                    cursor: pointer !important;
                    user-select: none !important;
                    -webkit-user-select: none !important;
                    -moz-user-select: none !important;
                    -ms-user-select: none !important;
                }
                
                /* Loading spinner */
                .loading-spinner {
                    font-size: 48px;
                    animation: spin 1s linear infinite;
                }
                
                @keyframes spin {
                    from { transform: rotate(0deg); }
                    to { transform: rotate(360deg); }
                }
                
                /* Sort indicators */
                .recipe-popup-table th {
                    cursor: pointer;
                    user-select: none;
                }
                
                .recipe-popup-table th.sort-asc::after {
                    content: " ↑";
                    color: #fce7c8;
                }
                
                .recipe-popup-table th.sort-desc::after {
                    content: " ↓";
                    color: #fce7c8;
                }
                
                /* Popup note */
                .popup-note {
                    background: rgba(252,231,200,.1);
                    border: 1px solid rgba(252,231,200,.3);
                    padding: 10px;
                    margin-bottom: 15px;
                    color: #E3BB7A;
                    text-align: center;
                }
                
                /* Recipe count */
                .recipe-count {
                    position: absolute;
                    right: 60px;
                    top: 50%;
                    transform: translateY(-50%);
                    color: #E3BB7A;
                    font-size: 14px;
                }
                
                /* Popup stats */
                .popup-stats {
                    color: #E3BB7A;
                    font-size: 13px;
                    margin-left: 15px;
                }
                
                /* Popup controls */
                .popup-controls {
                    display: flex;
                    align-items: center;
                    margin-bottom: 15px;
                    gap: 15px;
                }
                
                .popup-search-container {
                    flex: 1;
                }
            </style>`;
            
            $('head').append(styles);
        }
    }
    
    // Format tables to ensure proper styling
    function formatTables() {
        $('.recipe-table').each(function() {
            var $table = $(this);
            
            // Ensure proper header styling
            $table.find('thead tr').addClass('tr-dark');
            
            // Add wrapper if missing
            if (!$table.parent().hasClass('dune-table-wrapper')) {
                $table.wrap('<div class="dune-table-wrapper"></div>');
            }
        });
    }
    
    // Initialize table search functionality
    function initTableSearch() {
        // Create search inputs if they don't exist
        $('.crafting-section').each(function() {
            var $section = $(this);
            var $table = $section.find('.recipe-table');
            
            if ($table.length && !$section.find('.recipe-search-container').length) {
                var searchHtml = '<div class="recipe-search-container">' +
                    '<input type="text" class="recipe-search-input" placeholder="Search recipes...">' +
                    '</div>';
                $section.find('.dune-card-description').after(searchHtml);
            }
        });
        
        // Bind search functionality
        $(document).on('input', '.recipe-search-input', function() {
            var $input = $(this);
            var $table = $input.closest('.crafting-section').find('.recipe-table');
            filterTable($table, $input.val());
        });
    }
    
    // Filter table rows based on search input
    function filterTable($table, searchTerm) {
        var term = searchTerm.toLowerCase();
        
        $table.find('tbody tr').each(function() {
            var $row = $(this);
            var text = $row.text().toLowerCase();
            
            if (text.indexOf(term) > -1) {
                $row.show();
            } else {
                $row.hide();
            }
        });
    }
    
    // FIXED: Initialize popup handlers with event delegation
    function initPopupHandlers() {
        console.log('[ResourcePage] Setting up popup handlers...');
        
        // View All Crafted With - using event delegation
        $(document).on('click', '.view-all-crafted-with', function(e) {
            e.preventDefault();
            e.stopPropagation();
            console.log('[ResourcePage] View All Crafted With clicked');
            
            var $element = $(this);
            var resourceName = $element.data('resource') || $element.attr('data-resource');
            
            if (!resourceName) {
                console.error('[ResourcePage] No resource name found!');
                return false;
            }
            
            showRecipePopup('craftedWith', resourceName);
            return false;
        });
        
        // View All Crafted From - using event delegation
        $(document).on('click', '.view-all-crafted-from', function(e) {
            e.preventDefault();
            e.stopPropagation();
            console.log('[ResourcePage] View All Crafted From clicked');
            
            var $element = $(this);
            var resourceName = $element.data('resource') || $element.attr('data-resource');
            
            if (!resourceName) {
                console.error('[ResourcePage] No resource name found!');
                return false;
            }
            
            showRecipePopup('craftedFrom', resourceName);
            return false;
        });
        
        // Close popup - overlay click
        $(document).on('click', '.recipe-popup-overlay', function(e) {
            if (e.target === this) {
                console.log('[ResourcePage] Overlay clicked - closing popup');
                closeRecipePopup();
            }
        });
        
        // Close popup - close button
        $(document).on('click', '.popup-close', function(e) {
            e.preventDefault();
            e.stopPropagation();
            console.log('[ResourcePage] Close button clicked');
            closeRecipePopup();
            return false;
        });
        
        // Escape key to close popup
        $(document).on('keydown', function(e) {
            if (e.key === 'Escape' && $('.recipe-popup-overlay').length > 0) {
                console.log('[ResourcePage] Escape key pressed - closing popup');
                closeRecipePopup();
            }
        });
        
        // Prevent text selection on span buttons
        $(document).on('selectstart', '.dune-action-button', function(e) {
            e.preventDefault();
            return false;
        });
        
        // Add hover logging for debugging
        $(document).on('mouseenter', '.dune-action-button', function() {
            console.log('[ResourcePage] Hovering over button:', $(this).text());
        });
    }
    
    // Show recipe popup
    function showRecipePopup(type, resourceName) {
        console.log('[ResourcePage] Showing popup for:', type, resourceName);
        
        var title = type === 'craftedWith' 
            ? 'All Recipes Using ' + resourceName
            : 'All Recipes Creating ' + resourceName;
        
        // Show loading state
        showLoadingPopup();
        
        // First, check for full data in hidden elements
        var dataSelector = '.' + type.replace('craftedWith', 'crafted-with').replace('craftedFrom', 'crafted-from') + '-full-data';
        var $fullDataElement = $(dataSelector);
        
        console.log('[ResourcePage] Looking for data in:', dataSelector);
        
        if ($fullDataElement.length && $fullDataElement.text().trim()) {
            try {
                // Parse JSON data from hidden element
                var jsonText = $fullDataElement.text().trim();
                console.log('[ResourcePage] Found JSON data, parsing...');
                
                var jsonData = JSON.parse(jsonText);
                var rows = jsonData.results || jsonData || [];
                
                console.log('[ResourcePage] Parsed', rows.length, 'rows');
                
                // Transform to expected format
                var formattedRows = rows.map(function(row) {
                    return {
                        item: formatItemName(row.OutputItem || row.output_item || ''),
                        resources: formatResourceList(row.Resources || row.resources || ''),
                        stations: formatStations(row.Stations || row.station || row.stations || ''),
                        water: formatWater(row.WaterML || row.water_ml),
                        craftTime: formatCraftTime(row.CraftTime || row.craft_time),
                        schematic: formatSchematic(row.Schematic || row.schematic)
                    };
                });
                
                displayPopup(title, formattedRows, false);
                return;
            } catch (e) {
                console.error('[ResourcePage] Failed to parse full recipe data:', e);
            }
        }
        
        // Fallback to API call
        console.log('[ResourcePage] No cached data found, making API call...');
        fetchRecipeDataViaAPI(type, resourceName, title);
    }
    
    // Format item name (handle wiki links)
    function formatItemName(name) {
        if (!name) return 'Unknown';
        
        // Handle wiki links if present
        if (name.indexOf('[[') > -1) {
            return name.replace(/\[\[([^\|]+)\|?([^\]]*)\]\]/g, function(match, link, display) {
                return '<a href="/wiki/' + link.replace(/ /g, '_') + '">' + (display || link) + '</a>';
            });
        }
        return name;
    }
    
    // Format resource list for display
    function formatResourceList(resources) {
        if (!resources) return 'None';
        
        // If it's already HTML formatted, return as is
        if (resources.indexOf('<br') > -1) {
            return resources;
        }
        
        // Otherwise, split by semicolons or newlines and format
        return resources.split(/[;\n]/).map(function(r) {
            return r.trim();
        }).filter(Boolean).join('<br>');
    }
    
    // Format stations
    function formatStations(stations) {
        if (!stations) return 'Unknown';
        
        // Handle multiple stations
        return stations.split(/[;,]/).map(function(s) {
            return s.trim();
        }).filter(Boolean).join('<br>');
    }
    
    // Format water amount
    function formatWater(water) {
        if (!water || water === '0' || water === 'N/A') return 'N/A';
        return parseInt(water).toLocaleString() + ' mL';
    }
    
    // Format craft time
    function formatCraftTime(time) {
        if (!time || time === '0' || time === 'N/A') return 'Instant';
        
        var seconds = parseInt(time);
        if (isNaN(seconds)) return time;
        
        if (seconds < 60) return seconds + 's';
        if (seconds < 3600) {
            var mins = Math.floor(seconds / 60);
            var secs = seconds % 60;
            return mins + 'm ' + (secs > 0 ? secs + 's' : '');
        }
        
        var hours = Math.floor(seconds / 3600);
        var mins = Math.floor((seconds % 3600) / 60);
        return hours + 'h ' + (mins > 0 ? mins + 'm' : '');
    }
    
    // Format schematic
    function formatSchematic(schematic) {
        if (!schematic || schematic === 'N/A' || schematic === 'None') return 'None';
        return formatItemName(schematic);
    }
    
    // Fetch recipe data via API
    function fetchRecipeDataViaAPI(type, resourceName, title) {
        $.ajax({
            url: mw.util.wikiScript('api'),
            data: {
                action: 'parse',
                format: 'json',
                page: mw.config.get('wgPageName'),
                prop: 'text',
                disablelimitreport: true,
                disableeditsection: true,
                disabletoc: true
            },
            success: function(response) {
                if (response.parse && response.parse.text) {
                    var html = response.parse.text['*'];
                    var $html = $('<div>').html(html);
                    
                    // Try to find full data first
                    var dataSelector = '.' + type.replace('craftedWith', 'crafted-with').replace('craftedFrom', 'crafted-from') + '-full-data';
                    var $dataElement = $html.find(dataSelector);
                    
                    if ($dataElement.length && $dataElement.text().trim()) {
                        try {
                            var jsonData = JSON.parse($dataElement.text().trim());
                            var rows = (jsonData.results || jsonData || []).map(function(row) {
                                return {
                                    item: formatItemName(row.OutputItem || row.output_item || ''),
                                    resources: formatResourceList(row.Resources || row.resources || ''),
                                    stations: formatStations(row.Stations || row.station || row.stations || ''),
                                    water: formatWater(row.WaterML || row.water_ml),
                                    craftTime: formatCraftTime(row.CraftTime || row.craft_time),
                                    schematic: formatSchematic(row.Schematic || row.schematic)
                                };
                            });
                            
                            console.log('[ResourcePage] Found full data via API:', rows.length, 'rows');
                            displayPopup(title, rows, false);
                            return;
                        } catch (e) {
                            console.error('[ResourcePage] Failed to parse API JSON data:', e);
                        }
                    }
                    
                    // Fallback to parsing table
                    var tableId = type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable';
                    var $table = $html.find(tableId);
                    
                    var rows = [];
                    $table.find('tbody tr').each(function() {
                        var $cells = $(this).find('td');
                        if ($cells.length >= 3) {
                            rows.push({
                                item: $cells.eq(0).html(),
                                resources: $cells.eq(1).html(),
                                stations: $cells.eq(2).html(),
                                water: 'N/A',
                                craftTime: 'N/A',
                                schematic: 'N/A'
                            });
                        }
                    });
                    
                    console.log('[ResourcePage] Found', rows.length, 'rows via API table parsing');
                    displayPopup(title, rows, rows.length < 10); // Mark as limited if less than 10
                }
            },
            error: function(xhr, status, error) {
                console.error('[ResourcePage] API call failed:', error);
                var rows = getVisibleRows(type);
                displayPopup(title, rows, true);
            }
        });
    }
    
    // Get visible rows as fallback
    function getVisibleRows(type) {
        var $table = $(type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable');
        var rows = [];
        
        $table.find('tbody tr').each(function() {
            var $row = $(this);
            var $cells = $row.find('td');
            
            if ($cells.length >= 3) {
                rows.push({
                    item: $cells.eq(0).html(),
                    resources: $cells.eq(1).html(),
                    stations: $cells.eq(2).html(),
                    water: 'N/A',
                    craftTime: 'N/A',
                    schematic: 'N/A'
                });
            }
        });
        
        console.log('[ResourcePage] Using visible rows:', rows.length);
        return rows;
    }
    
    // Show loading popup
    function showLoadingPopup() {
        var loadingHtml = '<div class="recipe-popup-overlay active">' +
            '<div class="recipe-popup">' +
                '<div class="popup-content" style="text-align: center; padding: 60px;">' +
                    '<div style="font-size: 24px; color: #fce7c8; margin-bottom: 20px;">Loading recipes...</div>' +
                    '<div class="loading-spinner">⟳</div>' +
                '</div>' +
            '</div>' +
        '</div>';
        
        $('body').append(loadingHtml);
    }
    
    // Display popup with data
    function displayPopup(title, rows, isLimited) {
        // Remove any existing popup
        $('.recipe-popup-overlay').remove();
        
        // Build table rows HTML
        var tableRowsHtml = '';
        rows.forEach(function(row, index) {
            tableRowsHtml += '<tr data-index="' + index + '">' +
                '<td>' + row.item + '</td>' +
                '<td>' + row.resources + '</td>' +
                '<td>' + row.stations + '</td>' +
                '<td>' + row.water + '</td>' +
                '<td>' + row.craftTime + '</td>' +
                '<td>' + row.schematic + '</td>' +
            '</tr>';
        });
        
        // Note if data is limited
        var limitedNote = isLimited ? 
            '<div class="popup-note">Note: This may not show all recipes. Reload the page to see full data.</div>' : 
            '';
        
        // Build popup HTML
        var popupHtml = '<div class="recipe-popup-overlay">' +
            '<div class="recipe-popup">' +
                '<div class="popup-header">' +
                    '<h3 class="popup-title">' + title + '</h3>' +
                    '<span class="recipe-count">' + rows.length + ' recipe' + (rows.length !== 1 ? 's' : '') + '</span>' +
                    '<button class="popup-close">×</button>' +
                '</div>' +
                '<div class="popup-content">' +
                    limitedNote +
                    '<div class="popup-controls">' +
                        '<div class="popup-search-container">' +
                            '<input type="text" class="popup-search-input" placeholder="Search recipes..." id="popupSearchInput">' +
                        '</div>' +
                        '<div class="popup-stats">' +
                            '<span class="visible-count">' + rows.length + '</span> of <span class="total-count">' + rows.length + '</span> shown' +
                        '</div>' +
                    '</div>' +
                    '<div class="popup-table-wrapper">' +
                        '<table class="infobox-dune-standard-table recipe-popup-table" id="popupRecipeTable">' +
                            '<thead>' +
                                '<tr class="tr-dark">' +
                                    '<th data-sort="item" class="sortable">Item Created</th>' +
                                    '<th data-sort="resources" class="sortable">Resources Needed</th>' +
                                    '<th data-sort="stations" class="sortable">Stations</th>' +
                                    '<th data-sort="water" class="sortable">Water (mL)</th>' +
                                    '<th data-sort="time" class="sortable">Craft Time</th>' +
                                    '<th data-sort="schematic" class="sortable">Schematic</th>' +
                                '</tr>' +
                            '</thead>' +
                            '<tbody>' + tableRowsHtml + '</tbody>' +
                        '</table>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';
        
        // Add to page
        $('body').append(popupHtml);
        
        // Activate popup
        setTimeout(function() {
            $('.recipe-popup-overlay').addClass('active');
        }, 10);
        
        // Initialize popup functionality
        initPopupFunctionality();
    }
    
    // Initialize popup functionality (search and sort)
    function initPopupFunctionality() {
        var $searchInput = $('#popupSearchInput');
        var $visibleCount = $('.visible-count');
        var $totalCount = $('.total-count');
        var totalRows = $('#popupRecipeTable tbody tr').length;
        
        // Search functionality with counting
        $searchInput.on('input', function() {
            var searchTerm = $(this).val().toLowerCase();
            var visibleRows = 0;
            
            $('#popupRecipeTable tbody tr').each(function() {
                var $row = $(this);
                var text = $row.text().toLowerCase();
                
                if (searchTerm === '' || text.indexOf(searchTerm) > -1) {
                    $row.show();
                    visibleRows++;
                } else {
                    $row.hide();
                }
            });
            
            $visibleCount.text(visibleRows);
        });
        
        // Sort functionality
        $('#popupRecipeTable thead th.sortable').on('click', function() {
            var $th = $(this);
            var sortKey = $th.data('sort');
            var $tbody = $('#popupRecipeTable tbody');
            var rows = $tbody.find('tr').toArray();
            
            // Determine sort direction
            var ascending = !$th.hasClass('sort-desc');
            
            // Remove sort classes from all headers
            $('#popupRecipeTable thead th').removeClass('sort-asc sort-desc');
            
            // Add appropriate class to clicked header
            $th.addClass(ascending ? 'sort-asc' : 'sort-desc');
            
            // Sort rows
            rows.sort(function(a, b) {
                var aText = $(a).find('td').eq($th.index()).text().trim();
                var bText = $(b).find('td').eq($th.index()).text().trim();
                
                // Special handling for numeric columns
                if (sortKey === 'water') {
                    var aNum = parseInt(aText.replace(/[^\d]/g, '')) || 0;
                    var bNum = parseInt(bText.replace(/[^\d]/g, '')) || 0;
                    return ascending ? aNum - bNum : bNum - aNum;
                }
                
                if (sortKey === 'time') {
                    var aSeconds = parseTimeToSeconds(aText);
                    var bSeconds = parseTimeToSeconds(bText);
                    return ascending ? aSeconds - bSeconds : bSeconds - aSeconds;
                }
                
                // Sort as text
                return ascending 
                    ? aText.localeCompare(bText)
                    : bText.localeCompare(aText);
            });
            
            // Re-append sorted rows
            $tbody.empty();
            rows.forEach(function(row) {
                $tbody.append(row);
            });
        });
        
        // Focus search input
        $searchInput.focus();
    }
    
    // Parse time string to seconds for sorting
    function parseTimeToSeconds(timeStr) {
        if (!timeStr || timeStr === 'Instant' || timeStr === 'N/A') return 0;
        
        var seconds = 0;
        var hourMatch = timeStr.match(/(\d+)h/);
        var minMatch = timeStr.match(/(\d+)m/);
        var secMatch = timeStr.match(/(\d+)s/);
        
        if (hourMatch) seconds += parseInt(hourMatch[1]) * 3600;
        if (minMatch) seconds += parseInt(minMatch[1]) * 60;
        if (secMatch) seconds += parseInt(secMatch[1]);
        
        return seconds;
    }
    
    // Close recipe popup
    function closeRecipePopup() {
        $('.recipe-popup-overlay').removeClass('active');
        
        setTimeout(function() {
            $('.recipe-popup-overlay').remove();
        }, 300);
    }
    
    // Add required CSS for animations
    function addAnimationStyles() {
        if ($('#resourcePageAnimations').length === 0) {
            var styles = `
                <style id="resourcePageAnimations">
                    @keyframes spin {
                        from { transform: rotate(0deg); }
                        to { transform: rotate(360deg); }
                    }
                    
                    .recipe-popup-table th.sort-asc::after {
                        content: ' ↑';
                        color: #fce7c8;
                    }
                    
                    .recipe-popup-table th.sort-desc::after {
                        content: ' ↓';
                        color: #fce7c8;
                    }
                    
                    .recipe-popup-table th.sortable:hover {
                        background: rgba(252,231,200,.1) !important;
                        cursor: pointer;
                    }
                    
                    /* Gallery styles */
                    .gallery-container {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 10px;
                        margin-top: 10px;
                    }
                    
                    .gallery-item {
                        flex: 0 0 120px;
                        text-align: center;
                    }
                    
                    .gallery-item img {
                        max-width: 100%;
                        height: auto;
                        border: 2px solid rgba(252,231,200,.3);
                        transition: all 0.3s ease;
                    }
                    
                    .gallery-item:hover img {
                        border-color: #fce7c8;
                        transform: scale(1.05);
                        box-shadow: 0 0 15px rgba(252,231,200,.5);
                    }
                    
                    .gallery-item .caption {
                        font-size: 12px;
                        color: #E3BB7A;
                        margin-top: 4px;
                    }
                </style>
            `;
            $('head').append(styles);
        }
    }
    
    // Initialize when DOM is ready
    $(document).ready(function() {
        init();
    });
    
})(jQuery, mediaWiki);