Gadget-ResourcePage.js: Difference between revisions
From Dune Awakening DB
mNo edit summary |
mNo edit summary |
||
| Line 39: | Line 39: | ||
// Format tables | // Format tables | ||
formatTables(); | formatTables(); | ||
// Add animation styles | |||
addAnimationStyles(); | |||
} | } | ||
| Line 100: | Line 103: | ||
color: #E3BB7A; | color: #E3BB7A; | ||
font-size: 14px; | 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>`; | </style>`; | ||
| Line 114: | Line 136: | ||
// Ensure proper header styling | // Ensure proper header styling | ||
$table.find('thead tr').addClass('tr-dark'); | $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>'); | |||
} | |||
}); | }); | ||
} | } | ||
| Line 119: | Line 146: | ||
// Initialize table search functionality | // Initialize table search functionality | ||
function initTableSearch() { | 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 | // Filter table rows based on search input | ||
function filterTable( | function filterTable($table, searchTerm) { | ||
var term = searchTerm.toLowerCase(); | var term = searchTerm.toLowerCase(); | ||
| Line 216: | Line 252: | ||
e.preventDefault(); | e.preventDefault(); | ||
return false; | return false; | ||
}); | |||
// Add hover logging for debugging | |||
$(document).on('mouseenter', '.dune-action-button', function() { | |||
console.log('[ResourcePage] Hovering over button:', $(this).text()); | |||
}); | }); | ||
} | } | ||
| Line 250: | Line 291: | ||
var formattedRows = rows.map(function(row) { | var formattedRows = rows.map(function(row) { | ||
return { | return { | ||
item: row.OutputItem || row.output_item || '', | item: formatItemName(row.OutputItem || row.output_item || ''), | ||
resources: formatResourceList(row.Resources || row.resources || ''), | resources: formatResourceList(row.Resources || row.resources || ''), | ||
stations: row.Stations || row.station || row.stations || '', | stations: formatStations(row.Stations || row.station || row.stations || ''), | ||
water: row.WaterML || row.water_ml | water: formatWater(row.WaterML || row.water_ml), | ||
craftTime: row.CraftTime || row.craft_time | craftTime: formatCraftTime(row.CraftTime || row.craft_time), | ||
schematic: row.Schematic || row.schematic | schematic: formatSchematic(row.Schematic || row.schematic) | ||
}; | }; | ||
}); | }); | ||
| Line 269: | Line 310: | ||
console.log('[ResourcePage] No cached data found, making API call...'); | console.log('[ResourcePage] No cached data found, making API call...'); | ||
fetchRecipeDataViaAPI(type, resourceName, title); | 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 | // Format resource list for display | ||
function formatResourceList(resources) { | function formatResourceList(resources) { | ||
if (!resources) return ''; | if (!resources) return 'None'; | ||
// If it's already HTML formatted, return as is | // If it's already HTML formatted, return as is | ||
| Line 280: | Line 334: | ||
} | } | ||
// Otherwise, split by semicolons and format | // Otherwise, split by semicolons or newlines and format | ||
return resources.split( | return resources.split(/[;\n]/).map(function(r) { | ||
return r.trim(); | return r.trim(); | ||
}).filter(Boolean).join('<br>'); | }).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); | |||
} | } | ||
| Line 294: | Line 389: | ||
format: 'json', | format: 'json', | ||
page: mw.config.get('wgPageName'), | page: mw.config.get('wgPageName'), | ||
prop: 'text' | prop: 'text', | ||
disablelimitreport: true, | |||
disableeditsection: true, | |||
disabletoc: true | |||
}, | }, | ||
success: function(response) { | success: function(response) { | ||
| Line 301: | Line 399: | ||
var $html = $('<div>').html(html); | 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 tableId = type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable'; | ||
var $table = $html.find(tableId); | var $table = $html.find(tableId); | ||
| Line 320: | Line 444: | ||
}); | }); | ||
console.log('[ResourcePage] Found', rows.length, 'rows via API'); | console.log('[ResourcePage] Found', rows.length, 'rows via API table parsing'); | ||
displayPopup(title, rows, | displayPopup(title, rows, rows.length < 10); // Mark as limited if less than 10 | ||
} | } | ||
}, | }, | ||
error: function() { | error: function(xhr, status, error) { | ||
console.error('[ResourcePage] API call failed, | console.error('[ResourcePage] API call failed:', error); | ||
var rows = getVisibleRows(type); | var rows = getVisibleRows(type); | ||
displayPopup(title, rows, true); | displayPopup(title, rows, true); | ||
| Line 337: | Line 461: | ||
var rows = []; | var rows = []; | ||
$table.find('tbody tr | $table.find('tbody tr').each(function() { | ||
var $row = $(this); | var $row = $(this); | ||
var $cells = $row.find('td'); | var $cells = $row.find('td'); | ||
| Line 353: | Line 477: | ||
}); | }); | ||
console.log('[ResourcePage] Using visible rows:', rows.length); | |||
return rows; | return rows; | ||
} | } | ||
| Line 377: | Line 502: | ||
// Build table rows HTML | // Build table rows HTML | ||
var tableRowsHtml = ''; | var tableRowsHtml = ''; | ||
rows.forEach(function(row) { | rows.forEach(function(row, index) { | ||
tableRowsHtml += '<tr>' + | tableRowsHtml += '<tr data-index="' + index + '">' + | ||
'<td>' + row.item + '</td>' + | '<td>' + row.item + '</td>' + | ||
'<td>' + row.resources + '</td>' + | '<td>' + row.resources + '</td>' + | ||
| Line 390: | Line 515: | ||
// Note if data is limited | // Note if data is limited | ||
var limitedNote = isLimited ? | var limitedNote = isLimited ? | ||
'<div class="popup-note"> | '<div class="popup-note">Note: This may not show all recipes. Reload the page to see full data.</div>' : | ||
''; | ''; | ||
| Line 398: | Line 523: | ||
'<div class="popup-header">' + | '<div class="popup-header">' + | ||
'<h3 class="popup-title">' + title + '</h3>' + | '<h3 class="popup-title">' + title + '</h3>' + | ||
'<span class="recipe-count">' + rows.length + ' | '<span class="recipe-count">' + rows.length + ' recipe' + (rows.length !== 1 ? 's' : '') + '</span>' + | ||
'<button class="popup-close">×</button>' + | '<button class="popup-close">×</button>' + | ||
'</div>' + | '</div>' + | ||
'<div class="popup-content">' + | '<div class="popup-content">' + | ||
limitedNote + | limitedNote + | ||
'<div class="popup-search-container">' + | '<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>' + | ||
'<div class="popup-table-wrapper">' + | '<div class="popup-table-wrapper">' + | ||
| Line 410: | Line 540: | ||
'<thead>' + | '<thead>' + | ||
'<tr class="tr-dark">' + | '<tr class="tr-dark">' + | ||
'<th data-sort="item">Item Created</th>' + | '<th data-sort="item" class="sortable">Item Created</th>' + | ||
'<th data-sort="resources">Resources Needed</th>' + | '<th data-sort="resources" class="sortable">Resources Needed</th>' + | ||
'<th data-sort="stations">Stations</th>' + | '<th data-sort="stations" class="sortable">Stations</th>' + | ||
'<th data-sort="water">Water (mL)</th>' + | '<th data-sort="water" class="sortable">Water (mL)</th>' + | ||
'<th data-sort="time">Craft Time</th>' + | '<th data-sort="time" class="sortable">Craft Time</th>' + | ||
'<th data-sort="schematic">Schematic</th>' + | '<th data-sort="schematic" class="sortable">Schematic</th>' + | ||
'</tr>' + | '</tr>' + | ||
'</thead>' + | '</thead>' + | ||
| Line 439: | Line 569: | ||
// Initialize popup functionality (search and sort) | // Initialize popup functionality (search and sort) | ||
function initPopupFunctionality() { | 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 searchTerm = $(this).val().toLowerCase(); | ||
var visibleRows = 0; | |||
$('#popupRecipeTable tbody tr').each(function() { | $('#popupRecipeTable tbody tr').each(function() { | ||
| Line 447: | Line 583: | ||
var text = $row.text().toLowerCase(); | var text = $row.text().toLowerCase(); | ||
if (text.indexOf(searchTerm) > -1) { | if (searchTerm === '' || text.indexOf(searchTerm) > -1) { | ||
$row.show(); | $row.show(); | ||
visibleRows++; | |||
} else { | } else { | ||
$row.hide(); | $row.hide(); | ||
} | } | ||
}); | }); | ||
$visibleCount.text(visibleRows); | |||
}); | }); | ||
// Sort functionality | // Sort functionality | ||
$('#popupRecipeTable thead th').on('click', function() { | $('#popupRecipeTable thead th.sortable').on('click', function() { | ||
var $th = $(this); | var $th = $(this); | ||
var sortKey = $th.data('sort'); | var sortKey = $th.data('sort'); | ||
| Line 476: | Line 615: | ||
var bText = $(b).find('td').eq($th.index()).text().trim(); | var bText = $(b).find('td').eq($th.index()).text().trim(); | ||
// | // Special handling for numeric columns | ||
var aNum = | 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 ( | if (sortKey === 'time') { | ||
return ascending ? | var aSeconds = parseTimeToSeconds(aText); | ||
var bSeconds = parseTimeToSeconds(bText); | |||
return ascending ? aSeconds - bSeconds : bSeconds - aSeconds; | |||
} | } | ||
| Line 498: | Line 642: | ||
// Focus search input | // 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; | |||
} | } | ||
| Line 508: | Line 668: | ||
$('.recipe-popup-overlay').remove(); | $('.recipe-popup-overlay').remove(); | ||
}, 300); | }, 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 | // Initialize when DOM is ready | ||
$(function() { | $(document).ready(function() { | ||
init(); | init(); | ||
}); | }); | ||
})(jQuery, mediaWiki); | })(jQuery, mediaWiki); | ||
Latest revision as of 01:51, 4 June 2025
/**
* 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);