1/** 2 * Steam Player Stats - Additional Modules JavaScript 3 * Handles specific functionality for additional modules (achievements, inventory checks, etc.) 4 */ 5 6// Version must match ?ver= used by wp_enqueue_script('steam-player-stats-shared-utils', ...) so this 7// import resolves to the same URL as the separately enqueued copy instead of fetching a duplicate module. 8import { 9 SteamConfig, 10 formatPlaytime, 11 formatNumber, 12 escapeHtml, 13 makeAjaxRequest, 14 getGameIconUrl, 15 getErrorMessage, 16 showNotification, 17 resetButton 18} from './shared-utils.js?ver=1.8.17'; 19 20(function($) { 21 'use strict'; 22 23 // Module initialization 24 $(document).ready(function() { 25 setupEventHandlers(); 26 }); 27 28 /** 29 * Setup event handlers for module interactions 30 */ 31 function setupEventHandlers() { 32 // Handle module creation events from steam-player-stats-public.js 33 $(document).on('steamModuleCreated', function(event, moduleName, steamId, $container) { 34 initializeModuleSpecificFunctionality(moduleName, steamId, $container); 35 }); 36 37 // Handle inventory module specific initialization 38 $(document).on('steamInventoryModuleCreated', function(event, steamId, $module) { 39 loadTopGamesForInventory($module, steamId); 40 }); 41 42 // Handle achievements buttons 43 $(document).on('click', '.show-achievements', function() { 44 const $button = $(this); 45 const steamId = $button.data('steam-id'); 46 const appId = $button.data('appid'); 47 const gameName = $button.data('game-name'); 48 49 loadGameAchievements($button, steamId, appId, gameName); 50 }); 51 52 // Handle inventory check buttons 53 $(document).on('click', '.steam-inventory-check-btn', function() { 54 const $button = $(this); 55 const steamId = $button.data('steam-id'); 56 const appId = $button.data('app-id'); 57 const gameName = $button.data('game-name'); 58 59 checkGameInventory($button, steamId, appId, gameName); 60 }); 61 } 62 63 /** 64 * Initialize module-specific functionality 65 */ 66 function initializeModuleSpecificFunctionality(moduleName, steamId, $container) { 67 switch (moduleName) { 68 case 'games': 69 // Games module is mostly handled by the main file 70 break; 71 case 'friends': 72 // Friends module is mostly handled by the main file 73 break; 74 case 'inventory': 75 // Inventory module needs special handling 76 const $module = $container.find('.steam-inventory-module'); 77 if ($module.length) { 78 loadTopGamesForInventory($module, steamId); 79 } 80 break; 81 } 82 } 83 84 /** 85 * Load top 5 games for inventory checking 86 */ 87 function loadTopGamesForInventory($module, steamId) { 88 makeAjaxRequest('get_player_games', { steamId: steamId }) 89 .done(function(response) { 90 if (response.success && response.data.top5Games) { 91 const $gameInventories = $module.find('.steam-game-inventories'); 92 $gameInventories.html(renderInventoryButtons(response.data.top5Games, steamId)); 93 } else { 94 const $gameInventories = $module.find('.steam-game-inventories'); 95 $gameInventories.html('<p class="steam-no-data">' + SteamConfig.getString('noGames', 'No games found') + '</p>'); 96 } 97 }) 98 .fail(function() { 99 const $gameInventories = $module.find('.steam-game-inventories'); 100 $gameInventories.html('<p class="steam-no-data">' + SteamConfig.getString('error', 'Error loading data') + '</p>'); 101 }); 102 } 103 104 /** 105 * Render inventory check buttons 106 */ 107 function renderInventoryButtons(games, steamId) { 108 if (!games || games.length === 0) { 109 return '<p class="steam-no-data">' + SteamConfig.getString('noGames', 'No games found') + '</p>'; 110 } 111 112 let html = '<div class="steam-inventory-buttons">'; 113
114 games.forEach(function(game) { 115 html += ` 116 <button class="steam-btn steam-btn-primary steam-inventory-check-btn" 117 data-steam-id="${steamId}" 118 data-app-id="${game.appid}" 119 data-game-name="${escapeHtml(game.name)}"> 120 ${SteamConfig.getString('checkInventory', 'Check Inventory')}: ${escapeHtml(game.name)} 121 </button> 122 `; 123 }); 124 125 html += '</div>'; 126 return html; 127 } 128 129 /** 130 * Render inventory grid with grouping by rarity 131 */ 132 function renderInventoryGrid(data, gameName, appId) { 133 const items = data.items || []; 134 135 // Group items by rarity 136 const rarityOrder = ['rare', 'mythical', 'legendary', 'ancient', 'immortal', 'arcana', 'common', 'uncommon']; 137 const groupedItems = {}; 138 const rarityCounts = {}; 139 140 items.forEach(item => { 141 const rarity = item.rarity || 'common'; 142 if (!groupedItems[rarity]) { 143 groupedItems[rarity] = []; 144 rarityCounts[rarity] = 0; 145 } 146 groupedItems[rarity].push(item); 147 rarityCounts[rarity]++; 148 }); 149 150 // Sort rarity groups by custom order 151 const sortedRarities = Object.keys(groupedItems).sort((a, b) => { 152 const indexA = rarityOrder.indexOf(a.toLowerCase()); 153 const indexB = rarityOrder.indexOf(b.toLowerCase()); 154 if (indexA === -1 && indexB === -1) return a.localeCompare(b); 155 if (indexA === -1) return 1; 156 if (indexB === -1) return -1; 157 return indexA - indexB; 158 }); 159 160 let html = `<div class="steam-inventory-display" data-inventory-for="${appId}">`; 161 162 // Statistics header 163 html += `<div class="steam-inventory-header">`; 164 html += `<h5 class="steam-inventory-title">${escapeHtml(gameName)} - ${SteamConfig.getString('inventory', 'Inventory')}</h5>`; 165 html += `<div class="steam-inventory-stats">`; 166 html += `<span>${SteamConfig.getString('showing', 'Showing')} ${data.displayedCount} ${SteamConfig.getString('of', 'of')} ${data.itemCount} ${SteamConfig.getString('items', 'items')}</span>`; 167 if (data.hasMore) { 168 html += `<span class="steam-inventory-more"> (${SteamConfig.getString('moreAvailable', 'more available')})</span>`; 169 } 170 html += `</div>`; 171 html += `</div>`; 172 173 // Rarity groups 174 sortedRarities.forEach(rarity => { 175 const rarityItems = groupedItems[rarity]; 176 const firstItem = rarityItems[0]; 177 const rarityName = firstItem.rarity_name || rarity; 178 const rarityColor = firstItem.rarity_color || ''; 179 180 html += `<div class="steam-rarity-group">`; 181 html += `<div class="steam-rarity-header" data-rarity="${escapeHtml(rarity)}">`; 182 html += `<span class="steam-rarity-indicator" style="background-color: #${rarityColor}"></span>`; 183 html += `<span class="steam-rarity-name">${escapeHtml(rarityName)}</span>`; 184 html += `<span class="steam-rarity-count">(${rarityCounts[rarity]})</span>`; 185 html += `<span class="steam-rarity-toggle">â¼</span>`; 186 html += `</div>`; 187 188 html += `<div class="steam-inventory-grid">`; 189 rarityItems.forEach(item => { 190 html += renderInventoryItem(item, rarityColor); 191 }); 192 html += `</div>`; 193 html += `</div>`; 194 }); 195 196 html += `</div>`; 197 return html; 198 } 199 200 /** 201 * Render single inventory item 202 */ 203 function renderInventoryItem(item, rarityColor) { 204 const borderColor = rarityColor ? `#${rarityColor}` : '#2a475e'; 205 const nameColor = item.name_color ? `#${item.name_color}` : '#d2d2d2'; 206 const bgColor = item.background_color ? `#${item.background_color}` : ''; 207 208 let html = `<div class="steam-inventory-item" 209 data-item-name="${escapeHtml(item.name)}" 210 data-item-type="${escapeHtml(item.type)}" 211 data-item-rarity="${escapeHtml(item.rarity_name)}" 212 data-item-description="${escapeHtml(item.description)}" 213 data-item-tradable="${item.tradable}" 214 data-item-marketable="${item.marketable}" 215 style="border-color: ${borderColor};">`; 216 217 html += `<div class="steam-item-icon" style="${bgColor ? 'background-color: ' + bgColor : ''}">`; 218 if (item.icon_url) { 219 html += `<img src="${escapeHtml(item.icon_url)}" alt="${escapeHtml(item.name)}" loading="lazy" onerror="this.style.display='none'">`; 220 } else { 221 html += `<div class="steam-item-no-icon">?</div>`; 222 } 223 if (item.amount > 1) { 224 html += `<span class="steam-item-amount">x${item.amount}</span>`;
225 } 226 html += `</div>`; 227 228 html += `<div class="steam-item-name" style="color: ${nameColor}">${escapeHtml(item.name)}</div>`; 229 html += `</div>`; 230 231 return html; 232 } 233 234 /** 235 * Initialize inventory tooltips 236 */ 237 function initializeInventoryTooltips() { 238 const $items = $('.steam-inventory-item'); 239 240 // Remove old event handlers 241 $items.off('mouseenter mouseleave click'); 242 243 // Click handler to show tooltip 244 $items.on('click', function(e) { 245 e.stopPropagation(); 246 const $item = $(this); 247 248 // Remove any existing tooltips 249 $('.steam-item-tooltip').remove(); 250 251 const name = $item.data('item-name'); 252 const type = $item.data('item-type'); 253 const rarity = $item.data('item-rarity'); 254 const description = $item.data('item-description'); 255 const tradable = $item.data('item-tradable'); 256 const marketable = $item.data('item-marketable'); 257 258 // Don't show tooltip if we don't have a name 259 if (!name) return; 260 261 // Build tooltip HTML with close button 262 let tooltipHtml = `<div class="steam-item-tooltip">`; 263 tooltipHtml += `<button class="steam-tooltip-close" aria-label="Close">×</button>`; 264 tooltipHtml += `<div class="steam-tooltip-name">${escapeHtml(name)}</div>`; 265 if (type) { 266 tooltipHtml += `<div class="steam-tooltip-type">${escapeHtml(type)}</div>`; 267 } 268 if (rarity) { 269 tooltipHtml += `<div class="steam-tooltip-rarity">${escapeHtml(rarity)}</div>`; 270 } 271 if (description) { 272 tooltipHtml += `<div class="steam-tooltip-divider"></div>`; 273 tooltipHtml += `<div class="steam-tooltip-description">${escapeHtml(description)}</div>`; 274 } 275 276 // Add badges 277 const badges = []; 278 if (tradable) badges.push(SteamConfig.getString('tradable', 'Tradable')); 279 if (marketable) badges.push(SteamConfig.getString('marketable', 'Marketable')); 280 if (badges.length > 0) { 281 tooltipHtml += `<div class="steam-tooltip-badges">`; 282 badges.forEach(badge => { 283 tooltipHtml += `<span class="steam-tooltip-badge">${badge}</span>`; 284 }); 285 tooltipHtml += `</div>`; 286 } 287 288 tooltipHtml += `</div>`; 289 290 const $tooltip = $(tooltipHtml); 291 $('body').append($tooltip); 292 293 // Position tooltip using getBoundingClientRect for fixed positioning 294 const rect = this.getBoundingClientRect(); 295 const tooltipWidth = $tooltip.outerWidth(); 296 const tooltipHeight = $tooltip.outerHeight(); 297 298 // Center tooltip above item 299 let left = rect.left + (rect.width / 2) - (tooltipWidth / 2); 300 let top = rect.top - tooltipHeight - 10; 301 302 // Adjust if tooltip goes off screen horizontally 303 if (left < 10) left = 10; 304 if (left + tooltipWidth > window.innerWidth - 10) { 305 left = window.innerWidth - tooltipWidth - 10; 306 } 307 308 // If tooltip goes off top of screen, show below item 309 if (top < 10) { 310 top = rect.bottom + 10; 311 } 312 313 $tooltip.css({ 314 left: left + 'px', 315 top: top + 'px', 316 display: 'block', 317 opacity: '0' 318 }); 319 320 // Fade in animation 321 setTimeout(() => { 322 $tooltip.css('opacity', '1'); 323 }, 10); 324 325 // Close button handler 326 $tooltip.find('.steam-tooltip-close').on('click', function(e) { 327 e.stopPropagation(); 328 $tooltip.fadeOut(200, function() { 329 $(this).remove(); 330 }); 331 }); 332 333 // Stop propagation when clicking on tooltip 334 $tooltip.on('click', function(e) { 335 e.stopPropagation(); 336 }); 337 }); 338 339 // Close tooltip when clicking outside 340 $(document).off('click.tooltip').on('click.tooltip', function() { 341 $('.steam-item-tooltip').fadeOut(200, function() { 342 $(this).remove(); 343 }); 344 }); 345 346 // Close tooltip on ESC key 347 $(document).off('keydown.tooltip').on('keydown.tooltip', function(e) { 348 if (e.key === 'Escape') { 349 $('.steam-item-tooltip').fadeOut(200, function() { 350 $(this).remove(); 351 }); 352 } 353 }); 354 355 // Toggle rarity groups 356 $(document).off('click', '.steam-rarity-header').on('click', '.steam-rarity-header', function() { 357 const $header = $(this); 358 const $grid = $header.next('.steam-inventory-grid'); 359 const $toggle = $header.find('.steam-rarity-toggle'); 360 361 $grid.slideToggle(300); 362 $toggle.text($toggle.text() === 'â¼' ? 'â¶' : 'â¼'); 363 }); 364 } 365 366 /** 367 * Check specific game inventory 368 */ 369 function checkGameInventory($button, steamId, appId, gameName) { 370 const originalText = $button.text(); 371 const $container = $button.closest('.steam-inventory-buttons'); 372 373 // Check if inventory is already displayed for this game 374 const existingInventory = $container.find(`[data-inventory-for="${appId}"]`); 375 if (existingInventory.length > 0) { 376 // Toggle visibility 377 existingInventory.slideToggle(300); 378 return; 379 } 380 381 $button.text(SteamConfig.getString('checking', 'Checking...')).prop('disabled', true); 382 383 makeAjaxRequest('get_game_inventory', { 384 steamId: steamId, 385 appId: appId 386 }, 30000) 387 .done(function(response) { 388 if (response.success) { 389 const data = response.data; 390 391 if (data.accessible && data.hasItems && data.items && data.items.length > 0) { 392 // Render inventory grid 393 const inventoryHtml = renderInventoryGrid(data, gameName, appId); 394 $button.after(inventoryHtml); 395 396 // Update button text 397 const itemText = data.itemCount === 1 ? 398 SteamConfig.getString('item', 'item') : 399 SteamConfig.getString('items', 'items'); 400 $button.text(`${gameName}: ${data.itemCount} ${itemText}`).removeClass('steam-btn-primary').addClass('steam-btn-success'); 401 402 // Initialize tooltips 403 initializeInventoryTooltips(); 404 405 } else if (data.accessible) { 406 const message = `${gameName}: ${data.message || SteamConfig.getString('noItems', 'No items found')}`; 407 $button.text(message).removeClass('steam-btn-primary').addClass('steam-btn-success'); 408 } else { 409 const message = `${gameName}: ${data.message}`; 410 $button.text(message).removeClass('steam-btn-primary').addClass('steam-btn-success'); 411 } 412 } else { 413 $button.text(`${gameName}: ${response.data.message}`).removeClass('steam-btn-primary').addClass('steam-btn-error'); 414 } 415 }) 416 .fail(function(xhr, status, error) { 417 const message = getErrorMessage(status, error); 418 $button.text(`${gameName}: ${message}`).removeClass('steam-btn-primary').addClass('steam-btn-error'); 419 }) 420 .always(function() { 421 $button.prop('disabled', false); 422 }); 423 } 424 425 /** 426 * Load achievements for specific game 427 */ 428 function loadGameAchievements($button, steamId, appId, gameName) { 429 const $achievementSpan = $button.siblings('.achievement-info'); 430 431 if ($achievementSpan.html()) return; // ÐÑли даннÑе Ñже загÑÑÐ¶ÐµÐ½Ñ 432 433 $button.prop('disabled', true).text(SteamConfig.getString('loadingAchievements', 'Loading...')); 434 435 makeAjaxRequest('get_game_achievements', { 436 steamId: steamId, 437 appid: appId 438 }, 15000) 439 .done(function(response) { 440 if (response.success) { 441 const data = response.data; 442 $achievementSpan.html(` 443 <div class="achievement-progress">
444 <span>${SteamConfig.getString('achievementProgress', 'Achievement Progress')}: ${data.percentage}% (${data.completedCount}/${data.totalCount})</span> 445 <div class="achievement-bar"> 446 <div class="achievement-fill" style="width: ${data.percentage}%"></div> 447 </div> 448 </div> 449 `); 450 } else { 451 $achievementSpan.html(`<span class="achievement-error">${response.data.message || SteamConfig.getString('noAchievements', 'No achievements available')}</span>`); 452 } 453 }) 454 .fail(function(xhr, status, error) { 455 const message = getErrorMessage(status, error); 456 $achievementSpan.html(`<span class="achievement-error">${SteamConfig.getString('errorAchievements', 'Failed to load achievements')}</span>`); 457 }) 458 .always(function() { 459 $button.remove(); // Remove button after loading 460 }); 461 } 462 463 // Legacy functions for backward compatibility (if needed) 464 // These are now handled by the main file, but keeping for potential external calls 465 466 /** 467 * @deprecated Use events instead 468 * Legacy function for initializing additional modules 469 */ 470 window.initializeAdditionalModules = function() { 471 console.warn('initializeAdditionalModules is deprecated. Modules are now initialized via events.'); 472 // Event-based initialization is automatic 473 }; 474 475 /** 476 * Populate Games Module with data (legacy support) 477 * @deprecated This is now handled by the main file 478 */ 479 function populateGamesData($module, data, steamId) { 480 console.warn('populateGamesData is deprecated. Use the main file rendering.'); 481 // Implementation moved to steam-player-stats-public.js 482 } 483 484 /** 485 * Populate Friends Module with data (legacy support) 486 * @deprecated This is now handled by the main file 487 */ 488 function populateFriendsData($module, data) { 489 console.warn('populateFriendsData is deprecated. Use the main file rendering.'); 490 // Implementation moved to steam-player-stats-public.js 491 } 492 493 /** 494 * Populate Inventory Module with data (legacy support) 495 * @deprecated This is now handled by the main file 496 */ 497 function populateInventoryData($module, data, steamId) { 498 console.warn('populateInventoryData is deprecated. Use loadTopGamesForInventory.'); 499 if ($module && steamId) { 500 loadTopGamesForInventory($module, steamId); 501 } 502 } 503 504 // Export functions for potential external use (if needed) 505 window.SteamAdditionalModules = { 506 loadTopGamesForInventory: loadTopGamesForInventory, 507 checkGameInventory: checkGameInventory, 508 loadGameAchievements: loadGameAchievements, 509 renderInventoryButtons: renderInventoryButtons 510 }; 511 512})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.