MediaWiki:Gadget-AdventurerPlateMakerTestBuild.js
Jump to navigation
Jump to search
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.
/**
* MediaWiki Gadget: Image Category Viewer with Multiple Overlays
* Displays eleven dropdown menus with images from different categories
* and overlays the images in order from category 1 (base) to category 11 (top)
*/
(function() {
'use strict';
// Only run on specific pages
var currentPage = mw.config.get('wgPageName');
if (currentPage !== 'User:Dr_Agon/AdvPlateTest') {
return; // Exit if not on the correct page
}
// Configuration - change these to your desired category names
var categories = [
{ key: 'category6', name: 'Adventurer_Plate_Backings', label: 'Plate Backing', suffix: ' Plate Backing.png' },
{ key: 'category4', name: 'Adventurer_Plate_Base_Plates', label: 'Base Plate', suffix: ' Plate Base Plate.png' },
{ key: 'category5', name: 'Adventurer_Plate_Pattern_Overlays', label: 'Pattern Overlay', suffix: ' Plate Pattern Overlay.png' },
{ key: 'category10', name: 'Adventurer_Plate_Frames', label: 'Plate Frame', suffix: ' Plate Frame.png' },
{ key: 'category1', name: 'Portrait_Backgrounds', label: 'Portrait Background', suffix: ' Portrait Background.png' },
{ key: 'category2', name: 'Portrait_Frames', label: 'Portrait Frame', suffix: ' Portrait Frame.png' },
{ key: 'category3', name: 'Portrait_Decorations', label: 'Portrait Accent', suffix: ' Portrait Decoration.png' },
{ key: 'category9', name: 'Adventurer_Plate_Portrait_Frames', label: 'Plate Portrait Frame', suffix: ' Plate Portrait Frame.png' },
{ key: 'category7', name: 'Adventurer_Plate_Top_Borders', label: 'Top Border', suffix: ' Plate Top Border.png' },
{ key: 'category8', name: 'Adventurer_Plate_Bottom_Borders', label: 'Bottom Border', suffix: ' Plate Bottom Border.png' },
{ key: 'category11', name: 'Adventurer_Plate_Accents', label: 'Accent', suffix: ' Plate Accent.png' }
];
// Store current image URLs
var currentImages = {};
categories.forEach(function(cat) {
currentImages[cat.key] = null;
});
// Load saved selections from localStorage
function loadSavedSelections() {
try {
var saved = localStorage.getItem('adventurer_plate_selections');
if (saved) {
return JSON.parse(saved);
}
} catch (e) {
console.error('Error loading saved selections:', e);
}
return {};
}
// Save current selections to localStorage
function saveSelections(selections) {
try {
localStorage.setItem('adventurer_plate_selections', JSON.stringify(selections));
} catch (e) {
console.error('Error saving selections:', e);
}
}
// Track current selections
var currentSelections = loadSavedSelections();
// Track portrait orientation
var portraitOrientation = 'right'; // default
// Load saved orientation
function loadSavedOrientation() {
try {
var saved = localStorage.getItem('adventurer_plate_orientation');
if (saved) {
return saved;
}
} catch (e) {
console.error('Error loading saved orientation:', e);
}
return 'right';
}
// Save orientation to localStorage
function saveOrientation(orientation) {
try {
localStorage.setItem('adventurer_plate_orientation', orientation);
} catch (e) {
console.error('Error saving orientation:', e);
}
}
portraitOrientation = loadSavedOrientation();
// Preset system
var availablePresets = {
adventurerPlate: [],
portrait: []
};
// Acquisition data storage
var acquisitionData = {};
// Load presets from JSON file
function loadPresets() {
return fetch('https://ffxiv.consolegameswiki.com/wiki/Module:Gadget-AdventurerPlateMaker-PresetData.json?action=raw')
.then(function(response) {
if (!response.ok) {
throw new Error('Failed to load presets');
}
return response.json();
})
.then(function(data) {
availablePresets.adventurerPlate = data.adventurerPlatePresets || [];
availablePresets.portrait = data.portraitPresets || [];
console.log('Presets loaded:', availablePresets);
})
.catch(function(error) {
console.error('Error loading presets:', error);
});
}
// Load acquisition data from separate JSON file
function loadAcquisitionData() {
return fetch('https://ffxiv.consolegameswiki.com/wiki/Module:Gadget-AdventurerPlateMaker-AcquisitionData.json?action=raw')
.then(function(response) {
if (!response.ok) {
throw new Error('Failed to load acquisition data');
}
return response.json();
})
.then(function(data) {
// Convert array to object for easy lookup by name
if (Array.isArray(data)) {
data.forEach(function(item) {
acquisitionData[item.name] = item;
});
}
console.log('Acquisition data loaded:', acquisitionData);
})
.catch(function(error) {
console.error('Error loading acquisition data:', error);
});
}
// Parse MediaWiki syntax to HTML
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function parseMediaWikiSyntax(text) {
if (!text) return '';
// Replace wiki links [[Page]] or [[Page|Display Text]]
text = text.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, function(match, target, display) {
return '<a href="/wiki/' + encodeURIComponent(target.replace(/ /g, '_')) + '">' + escapeHtml(display) + '</a>';
});
text = text.replace(/\[\[([^\]]+)\]\]/g, function(match, page) {
return '<a href="/wiki/' + encodeURIComponent(page.replace(/ /g, '_')) + '">' + escapeHtml(page) + '</a>';
});
return text;
}
// Update acquisition display
function updateAcquisitionDisplay() {
var acquisitionContainer = document.getElementById('acquisition-display');
if (!acquisitionContainer) return;
acquisitionContainer.innerHTML = '';
// Collect all selected element names (without "File:" prefix and suffixes)
var selectedElements = new Set();
// Include both portrait categories (1-3) and adventurer plate categories (4-11)
var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
allCategories.forEach(function(categoryKey) {
if (currentSelections[categoryKey]) {
var cat = categories.find(function(c) { return c.key === categoryKey; });
if (cat) {
var filename = currentSelections[categoryKey];
var displayName = filename.replace('File:', '');
// Remove the suffix if present
if (cat.suffix && displayName.endsWith(cat.suffix)) {
displayName = displayName.substring(0, displayName.length - cat.suffix.length);
}
selectedElements.add(displayName);
}
}
});
// Display acquisition info for each unique element
if (selectedElements.size > 0) {
var hasAcquisitionData = false;
selectedElements.forEach(function(elementName) {
if (acquisitionData[elementName] && acquisitionData[elementName].acquisition) {
hasAcquisitionData = true;
var acquisitionItem = document.createElement('div');
acquisitionItem.className = 'apm-acquisition-item';
var nameSpan = document.createElement('strong');
nameSpan.textContent = elementName + ': ';
acquisitionItem.appendChild(nameSpan);
// Add framer's kit icon and link if specified
if (acquisitionData[elementName]['framers-kit']) {
var framersKitIcon = document.createElement('img');
framersKitIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/c/c0/Summoner_framers_kit_icon1.png';
framersKitIcon.alt = "Framer's Kit";
framersKitIcon.title = "Framer's Kit";
framersKitIcon.style.cssText = 'height: 20px; width: 20px; vertical-align: middle; margin-right: 4px;';
acquisitionItem.appendChild(framersKitIcon);
var framersKitLink = document.createElement('a');
framersKitLink.href = '/wiki/' + encodeURIComponent((acquisitionData[elementName]['framers-kit'] + " Framer's Kit").replace(/ /g, '_'));
framersKitLink.textContent = acquisitionData[elementName]['framers-kit'] + " Framer's Kit";
acquisitionItem.appendChild(framersKitLink);
acquisitionItem.appendChild(document.createTextNode(' - '));
}
// Parse MediaWiki syntax and insert as HTML
var parsedAcquisition = parseMediaWikiSyntax(acquisitionData[elementName].acquisition);
var acquisitionSpan = document.createElement('span');
acquisitionSpan.innerHTML = parsedAcquisition;
acquisitionItem.appendChild(acquisitionSpan);
acquisitionContainer.appendChild(acquisitionItem);
}
});
if (!hasAcquisitionData) {
acquisitionContainer.innerHTML = '<div class="apm-acquisition-empty">No acquisition information available for selected elements.</div>';
}
}
}
// Apply a preset
function applyPreset(preset, dropdowns) {
// Map preset keys to category keys
var keyMapping = {
'basePlate': 'category4',
'patternOverlay': 'category5',
'plateBacking': 'category6',
'topBorder': 'category7',
'bottomBorder': 'category8',
'platePortraitFrame': 'category9',
'plateFrame': 'category10',
'accent': 'category11',
'portraitBackground': 'category1',
'portraitFrame': 'category2',
'portraitAccent': 'category3'
};
// First, clear all relevant categories based on preset type
var categoriesToClear = [];
if (preset.type === 'adventurerPlate') {
// Clear all adventurer plate categories (4-11)
categoriesToClear = ['category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
} else if (preset.type === 'portrait') {
// Clear only portrait categories (1-3), NOT category9 (Plate Portrait Frame)
categoriesToClear = ['category1', 'category2', 'category3'];
}
categoriesToClear.forEach(function(categoryKey) {
if (dropdowns[categoryKey]) {
dropdowns[categoryKey].value = '';
currentImages[categoryKey] = null;
delete currentSelections[categoryKey];
}
});
// Then apply each preset value (portrait presets should not include platePortraitFrame)
Object.keys(preset.selections).forEach(function(key) {
var categoryKey = keyMapping[key];
var value = preset.selections[key];
// Skip platePortraitFrame if this is a portrait preset
if (preset.type === 'portrait' && key === 'platePortraitFrame') {
return;
}
if (categoryKey && dropdowns[categoryKey]) {
var dropdown = dropdowns[categoryKey];
if (value === null || value === '(None)') {
// Already cleared above, skip
return;
} else {
// Find matching option in dropdown
var found = false;
for (var i = 0; i < dropdown.options.length; i++) {
var option = dropdown.options[i];
if (option.textContent === value) {
dropdown.value = option.value;
found = true;
break;
}
}
if (found && dropdown.value) {
// Trigger load
loadImage(dropdown.value, categoryKey);
currentSelections[categoryKey] = dropdown.value;
}
}
}
});
saveSelections(currentSelections);
updateCompositeDisplay();
updateAcquisitionDisplay();
// Keep the preset name displayed in the dropdown (no reset)
// The dropdown value is already set to the preset name by the change event
}
// Generate shareable URL with current selections
function generateShareableUrl() {
var baseUrl = window.location.origin + window.location.pathname;
var params = new URLSearchParams();
// Add each selection as a parameter
Object.keys(currentSelections).forEach(function(categoryKey) {
var cat = categories.find(function(c) { return c.key === categoryKey; });
if (cat && currentSelections[categoryKey]) {
// Get the display name
var filename = currentSelections[categoryKey];
var displayName = filename.replace('File:', '');
if (cat.suffix && displayName.endsWith(cat.suffix)) {
displayName = displayName.substring(0, displayName.length - cat.suffix.length);
}
// Map category key to URL parameter name
var paramName = '';
switch(categoryKey) {
case 'category1': paramName = 'portraitBackground'; break;
case 'category2': paramName = 'portraitFrame'; break;
case 'category3': paramName = 'portraitAccent'; break;
case 'category4': paramName = 'basePlate'; break;
case 'category5': paramName = 'patternOverlay'; break;
case 'category6': paramName = 'plateBacking'; break;
case 'category7': paramName = 'topBorder'; break;
case 'category8': paramName = 'bottomBorder'; break;
case 'category9': paramName = 'platePortraitFrame'; break;
case 'category10': paramName = 'plateFrame'; break;
case 'category11': paramName = 'accent'; break;
}
if (paramName) {
params.set(paramName, displayName);
}
}
});
// Add orientation
params.set('orientation', portraitOrientation);
return baseUrl + '?' + params.toString();
}
// Load selections from URL parameters
function loadFromUrl(dropdowns) {
var params = new URLSearchParams(window.location.search);
// Check for preset parameter first
var presetName = params.get('preset');
if (presetName) {
// Find and apply the preset
var allPresets = availablePresets.adventurerPlate.concat(availablePresets.portrait);
var preset = allPresets.find(function(p) {
return p.name.toLowerCase() === presetName.toLowerCase();
});
if (preset) {
console.log('Loading preset from URL:', presetName);
applyPreset(preset, dropdowns);
return;
}
}
// Otherwise load individual selections from URL
var paramMapping = {
'portraitBackground': 'category1',
'portraitFrame': 'category2',
'portraitAccent': 'category3',
'basePlate': 'category4',
'patternOverlay': 'category5',
'plateBacking': 'category6',
'topBorder': 'category7',
'bottomBorder': 'category8',
'platePortraitFrame': 'category9',
'plateFrame': 'category10',
'accent': 'category11'
};
var hasUrlParams = false;
Object.keys(paramMapping).forEach(function(paramName) {
var value = params.get(paramName);
if (value) {
hasUrlParams = true;
var categoryKey = paramMapping[paramName];
var dropdown = dropdowns[categoryKey];
if (dropdown) {
// Find matching option
for (var i = 0; i < dropdown.options.length; i++) {
var option = dropdown.options[i];
if (option.textContent === value) {
dropdown.value = option.value;
if (dropdown.value) {
loadImage(dropdown.value, categoryKey);
currentSelections[categoryKey] = dropdown.value;
}
break;
}
}
}
}
});
// Load orientation from URL
var orientation = params.get('orientation');
if (orientation === 'left' || orientation === 'right') {
portraitOrientation = orientation;
saveOrientation(orientation);
// Update radio buttons
var leftRadio = document.getElementById('orientation-left');
var rightRadio = document.getElementById('orientation-right');
if (leftRadio && rightRadio) {
leftRadio.checked = (orientation === 'left');
rightRadio.checked = (orientation === 'right');
}
}
if (hasUrlParams) {
saveSelections(currentSelections);
updateCompositeDisplay();
}
}
// Populate preset dropdown
function populatePresetDropdown(dropdown, presetType, dropdowns) {
dropdown.innerHTML = '';
var presets = presetType === 'adventurerPlate' ? availablePresets.adventurerPlate : availablePresets.portrait;
// Add default option
var defaultOption = document.createElement('option');
defaultOption.textContent = '-- Select a preset --';
defaultOption.value = '';
dropdown.appendChild(defaultOption);
// Add preset options
if (presets.length === 0) {
var noPresetsOption = document.createElement('option');
noPresetsOption.textContent = 'No presets available';
noPresetsOption.value = '';
noPresetsOption.disabled = true;
dropdown.appendChild(noPresetsOption);
return;
}
// Sort presets alphabetically by name
presets.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
presets.forEach(function(preset) {
var option = document.createElement('option');
option.value = preset.name;
option.textContent = preset.name;
option.presetData = preset; // Store preset data on the option
dropdown.appendChild(option);
});
// Add "Custom" option
var customOption = document.createElement('option');
customOption.value = 'custom';
customOption.textContent = 'Custom';
customOption.disabled = true;
dropdown.appendChild(customOption);
// Add change event listener
dropdown.addEventListener('change', function() {
if (this.value && this.value !== 'custom') {
// Find the selected preset
var selectedOption = this.options[this.selectedIndex];
if (selectedOption.presetData) {
applyPreset(selectedOption.presetData, dropdowns);
// Don't reset - keep showing the selected preset name
}
}
});
}
// Create the UI container
function createUI() {
var container = document.createElement('div');
container.id = 'image-category-viewer';
container.style.cssText = 'margin: 20px;';
// Dropdowns container
var dropdownContainer = document.createElement('div');
dropdownContainer.style.cssText = 'margin-bottom: 10px;';
var dropdowns = {};
// Portrait Elements section (in its own gray box)
var portraitSection = document.createElement('div');
portraitSection.className = 'apm-section apm-section-inline';
portraitSection.style.cssText = 'margin-bottom: 10px;';
// Portrait header row: Icon and Load Portrait Preset
var portraitHeaderRow = document.createElement('div');
portraitHeaderRow.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px; align-items: center; justify-content: space-between; min-width: 600px;';
// Left side: Icon and preset dropdown
var portraitHeaderLeft = document.createElement('div');
portraitHeaderLeft.style.cssText = 'display: flex; gap: 15px; align-items: center;';
// Icon wrapper to match the first column width
var portraitIconWrapper = document.createElement('div');
portraitIconWrapper.style.cssText = 'flex: 0 0 200px; display: flex; align-items: center;';
var portraitIcon = document.createElement('img');
portraitIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/5/5b/Portrait_icon1.png';
portraitIcon.alt = 'Portrait Elements';
portraitIcon.style.cssText = 'height: 40px; width: 40px;';
portraitIconWrapper.appendChild(portraitIcon);
// Label between icon and dropdown
var portraitPresetLabel = document.createElement('label');
portraitPresetLabel.textContent = 'Load Portrait Preset:';
portraitPresetLabel.style.cssText = 'margin-left: 15px; white-space: nowrap; font-weight: bold;';
portraitIconWrapper.appendChild(portraitPresetLabel);
portraitHeaderLeft.appendChild(portraitIconWrapper);
// Load Portrait Preset dropdown (aligned with second column - Portrait Frame)
var portraitPresetWrapper = document.createElement('div');
portraitPresetWrapper.style.cssText = 'flex: 0 0 200px;';
var portraitPresetDropdown = document.createElement('select');
portraitPresetDropdown.id = 'portrait-preset-dropdown';
portraitPresetDropdown.className = 'apm-dropdown';
portraitPresetWrapper.appendChild(portraitPresetDropdown);
portraitHeaderLeft.appendChild(portraitPresetWrapper);
portraitHeaderRow.appendChild(portraitHeaderLeft);
// Right side: Exclude Crystalline Conflict checkbox
var excludeConflictWrapper = document.createElement('div');
excludeConflictWrapper.style.cssText = 'display: flex; align-items: center; gap: 5px;';
var excludeConflictCheckbox = document.createElement('input');
excludeConflictCheckbox.type = 'checkbox';
excludeConflictCheckbox.id = 'exclude-conflict-checkbox';
excludeConflictCheckbox.style.cssText = 'cursor: pointer;';
excludeConflictWrapper.appendChild(excludeConflictCheckbox);
var excludeConflictLabel = document.createElement('label');
excludeConflictLabel.textContent = 'Exclude Crystalline Conflict portraits from randomizer';
excludeConflictLabel.htmlFor = 'exclude-conflict-checkbox';
excludeConflictLabel.style.cssText = 'cursor: pointer; white-space: nowrap;';
excludeConflictWrapper.appendChild(excludeConflictLabel);
portraitHeaderRow.appendChild(excludeConflictWrapper);
portraitSection.appendChild(portraitHeaderRow);
// Portrait elements row: Background, Frame, Accent, Orientation
var portraitRow = document.createElement('div');
portraitRow.style.cssText = 'display: flex; gap: 15px; align-items: flex-end; min-width: 600px;';
// Portrait Backgrounds, Portrait Frames, Portrait Decorations
var portraitCategories = [
categories.find(function(c) { return c.key === 'category1'; }),
categories.find(function(c) { return c.key === 'category2'; }),
categories.find(function(c) { return c.key === 'category3'; })
];
portraitCategories.forEach(function(cat) {
var dropdownWrapper = document.createElement('div');
dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
var label = document.createElement('label');
label.textContent = cat.label + ': ';
label.style.marginBottom = '5px';
dropdownWrapper.appendChild(label);
var dropdown = document.createElement('select');
dropdown.id = 'image-dropdown-' + cat.key;
dropdown.className = 'apm-dropdown';
var defaultOption = document.createElement('option');
defaultOption.textContent = 'Loading images...';
defaultOption.value = '';
dropdown.appendChild(defaultOption);
dropdownWrapper.appendChild(dropdown);
portraitRow.appendChild(dropdownWrapper);
dropdowns[cat.key] = dropdown;
});
// Portrait Orientation selector (after portrait dropdowns)
var orientationWrapper = document.createElement('div');
orientationWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 auto;';
var orientationLabel = document.createElement('label');
orientationLabel.textContent = 'Portrait Orientation:';
orientationLabel.style.cssText = 'margin-bottom: 5px;';
orientationWrapper.appendChild(orientationLabel);
var orientationControls = document.createElement('div');
orientationControls.style.cssText = 'display: flex; gap: 10px; min-height: 30px; align-items: center; box-sizing: border-box;';
var leftRadio = document.createElement('input');
leftRadio.type = 'radio';
leftRadio.name = 'portrait-orientation';
leftRadio.value = 'left';
leftRadio.id = 'orientation-left';
leftRadio.checked = portraitOrientation === 'left';
var leftLabel = document.createElement('label');
leftLabel.htmlFor = 'orientation-left';
leftLabel.textContent = 'Left';
leftLabel.style.cursor = 'pointer';
var rightRadio = document.createElement('input');
rightRadio.type = 'radio';
rightRadio.name = 'portrait-orientation';
rightRadio.value = 'right';
rightRadio.id = 'orientation-right';
rightRadio.checked = portraitOrientation === 'right';
var rightLabel = document.createElement('label');
rightLabel.htmlFor = 'orientation-right';
rightLabel.textContent = 'Right';
rightLabel.style.cursor = 'pointer';
orientationControls.appendChild(leftRadio);
orientationControls.appendChild(leftLabel);
orientationControls.appendChild(rightRadio);
orientationControls.appendChild(rightLabel);
orientationWrapper.appendChild(orientationControls);
// Add event listeners
rightRadio.addEventListener('change', function() {
if (this.checked) {
portraitOrientation = 'right';
saveOrientation('right');
updateCompositeDisplay();
}
});
leftRadio.addEventListener('change', function() {
if (this.checked) {
portraitOrientation = 'left';
saveOrientation('left');
updateCompositeDisplay();
}
});
portraitRow.appendChild(orientationWrapper);
portraitSection.appendChild(portraitRow);
// Adventurer Plate Elements section (in its own gray box)
var advPlateSection = document.createElement('div');
advPlateSection.className = 'apm-section apm-section-inline';
// Adventurer Plate header row: Icon and Load Plate Preset
var advPlateHeaderRow = document.createElement('div');
advPlateHeaderRow.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px; align-items: center; min-width: 600px;';
// Icon wrapper to match the first column width
var advPlateIconWrapper = document.createElement('div');
advPlateIconWrapper.style.cssText = 'flex: 0 0 200px; display: flex; align-items: center;';
var advPlateIcon = document.createElement('img');
advPlateIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/f/f4/Adventurer_plate_icon1.png';
advPlateIcon.alt = 'Adventurer Plate Elements';
advPlateIcon.style.cssText = 'height: 40px; width: 40px;';
advPlateIconWrapper.appendChild(advPlateIcon);
// Label between icon and dropdown
var presetLabel = document.createElement('label');
presetLabel.textContent = 'Load Plate Preset:';
presetLabel.style.cssText = 'margin-left: 15px; white-space: nowrap; font-weight: bold;';
advPlateIconWrapper.appendChild(presetLabel);
advPlateHeaderRow.appendChild(advPlateIconWrapper);
// Load Plate Preset dropdown (aligned with second column - Base Plate)
var presetWrapper = document.createElement('div');
presetWrapper.style.cssText = 'flex: 0 0 200px;';
var advPlatePresetDropdown = document.createElement('select');
advPlatePresetDropdown.id = 'adventurer-plate-preset-dropdown';
advPlatePresetDropdown.className = 'apm-dropdown';
presetWrapper.appendChild(advPlatePresetDropdown);
advPlateHeaderRow.appendChild(presetWrapper);
advPlateSection.appendChild(advPlateHeaderRow);
// Row 1: Backings, Base Plates, Pattern Overlays, Frames
var advPlateRow1 = document.createElement('div');
advPlateRow1.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px; min-width: 600px;';
var row1Categories = [
categories.find(function(c) { return c.key === 'category6'; }),
categories.find(function(c) { return c.key === 'category4'; }),
categories.find(function(c) { return c.key === 'category5'; }),
categories.find(function(c) { return c.key === 'category10'; })
];
row1Categories.forEach(function(cat) {
var dropdownWrapper = document.createElement('div');
dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
var label = document.createElement('label');
label.textContent = cat.label + ': ';
label.style.marginBottom = '5px';
dropdownWrapper.appendChild(label);
var dropdown = document.createElement('select');
dropdown.id = 'image-dropdown-' + cat.key;
dropdown.className = 'apm-dropdown';
var defaultOption = document.createElement('option');
defaultOption.textContent = 'Loading images...';
defaultOption.value = '';
dropdown.appendChild(defaultOption);
dropdownWrapper.appendChild(dropdown);
advPlateRow1.appendChild(dropdownWrapper);
dropdowns[cat.key] = dropdown;
});
advPlateSection.appendChild(advPlateRow1);
// Row 2: Portrait Frames, Top Borders, Bottom Borders, Accents
var advPlateRow2 = document.createElement('div');
advPlateRow2.style.cssText = 'display: flex; gap: 15px; min-width: 600px;';
var row2Categories = [
categories.find(function(c) { return c.key === 'category9'; }),
categories.find(function(c) { return c.key === 'category7'; }),
categories.find(function(c) { return c.key === 'category8'; }),
categories.find(function(c) { return c.key === 'category11'; })
];
row2Categories.forEach(function(cat) {
var dropdownWrapper = document.createElement('div');
dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
var label = document.createElement('label');
label.textContent = cat.label + ': ';
label.style.marginBottom = '5px';
dropdownWrapper.appendChild(label);
var dropdown = document.createElement('select');
dropdown.id = 'image-dropdown-' + cat.key;
dropdown.className = 'apm-dropdown';
var defaultOption = document.createElement('option');
defaultOption.textContent = 'Loading images...';
defaultOption.value = '';
dropdown.appendChild(defaultOption);
dropdownWrapper.appendChild(dropdown);
advPlateRow2.appendChild(dropdownWrapper);
dropdowns[cat.key] = dropdown;
});
advPlateSection.appendChild(advPlateRow2);
// Create tabber structure
var tabberWrapper = document.createElement('div');
tabberWrapper.style.cssText = 'display: flex; flex-direction: column; vertical-align: top; max-width: 100%; flex-shrink: 1; min-width: 0;';
// Tab buttons
var tabButtons = document.createElement('div');
tabButtons.style.cssText = 'display: flex; gap: 5px; margin-bottom: 10px;';
var portraitTabButton = document.createElement('button');
portraitTabButton.textContent = 'Portrait';
portraitTabButton.className = 'apm-tab-button apm-tab-button-active';
portraitTabButton.id = 'portrait-tab-button';
var plateTabButton = document.createElement('button');
plateTabButton.textContent = 'Adventurer Plate';
plateTabButton.className = 'apm-tab-button apm-tab-button-inactive';
plateTabButton.id = 'plate-tab-button';
tabButtons.appendChild(portraitTabButton);
tabButtons.appendChild(plateTabButton);
tabberWrapper.appendChild(tabButtons);
// Tab content container
var tabContent = document.createElement('div');
tabContent.style.cssText = 'position: relative; max-width: 100%; box-sizing: border-box;';
// Portrait tab content (visible by default)
portraitSection.className = 'apm-section apm-section-block';
portraitSection.style.display = 'block';
portraitSection.id = 'portrait-tab-content';
// Adventurer Plate tab content (hidden by default)
advPlateSection.className = 'apm-section apm-section-block';
advPlateSection.style.display = 'none';
advPlateSection.id = 'plate-tab-content';
tabContent.appendChild(portraitSection);
tabContent.appendChild(advPlateSection);
tabberWrapper.appendChild(tabContent);
// Tab switching logic
portraitTabButton.addEventListener('click', function() {
document.getElementById('portrait-tab-content').style.display = 'block';
document.getElementById('plate-tab-content').style.display = 'none';
portraitTabButton.className = 'apm-tab-button apm-tab-button-active';
plateTabButton.className = 'apm-tab-button apm-tab-button-inactive';
});
plateTabButton.addEventListener('click', function() {
document.getElementById('portrait-tab-content').style.display = 'none';
document.getElementById('plate-tab-content').style.display = 'block';
portraitTabButton.className = 'apm-tab-button apm-tab-button-inactive';
plateTabButton.className = 'apm-tab-button apm-tab-button-active';
});
// Buttons section (always to the right)
var buttonsSection = document.createElement('div');
buttonsSection.style.cssText = 'display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; margin-left: 10px; margin-top: 41px;';
// Clear All button
var clearButton = document.createElement('button');
clearButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/6/64/AdvPlateMaker-clear.png" alt="Clear All" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
clearButton.title = 'Clear All';
clearButton.className = 'apm-button apm-button-clear';
clearButton.addEventListener('click', function() {
// Clear all dropdowns
Object.keys(dropdowns).forEach(function(key) {
dropdowns[key].value = '';
});
// Clear current images
Object.keys(currentImages).forEach(function(key) {
currentImages[key] = null;
});
// Clear saved selections
currentSelections = {};
saveSelections({});
// Update display
updateCompositeDisplay();
});
buttonsSection.appendChild(clearButton);
// Randomize button
var randomizeButton = document.createElement('button');
randomizeButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/2/2b/AdvPlateMaker-randomizer.png" alt="Randomize" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
randomizeButton.title = 'Randomize';
randomizeButton.className = 'apm-button apm-button-randomize';
var randomizeOnCooldown = false;
randomizeButton.addEventListener('click', function() {
// Check if on cooldown
if (randomizeOnCooldown) {
return;
}
// Set cooldown
randomizeOnCooldown = true;
randomizeButton.style.opacity = '0.5';
randomizeButton.style.cursor = 'not-allowed';
// Randomize each dropdown (except preset dropdowns)
Object.keys(dropdowns).forEach(function(key) {
// Skip the preset dropdowns
if (key === 'presetDropdown' || key === 'portraitPresetDropdown') {
return;
}
var dropdown = dropdowns[key];
var options = dropdown.options;
// Check if we should exclude Conflict portraits for portrait categories
var excludeConflict = document.getElementById('exclude-conflict-checkbox').checked;
var isPortraitCategory = (key === 'category1' || key === 'category2' || key === 'category3');
// Get all valid options (exclude the default "-- Select an image --" option)
var validOptions = [];
for (var i = 0; i < options.length; i++) {
if (options[i].value !== '') {
// If excluding Conflict and this is a portrait category, check if option contains "Conflict"
if (excludeConflict && isPortraitCategory && options[i].textContent.indexOf('Conflict') !== -1) {
continue; // Skip this option
}
validOptions.push(options[i].value);
}
}
// Select a random option if available
if (validOptions.length > 0) {
var randomIndex = Math.floor(Math.random() * validOptions.length);
dropdown.value = validOptions[randomIndex];
// Trigger the change event to load the image
var event = new Event('change');
dropdown.dispatchEvent(event);
}
});
// Set preset dropdowns to "Custom" after randomizing
var presetDropdown = document.getElementById('adventurer-plate-preset-dropdown');
if (presetDropdown) {
presetDropdown.value = 'custom';
}
var portraitPresetDropdown = document.getElementById('portrait-preset-dropdown');
if (portraitPresetDropdown) {
portraitPresetDropdown.value = 'custom';
}
// Remove cooldown after 1.5 seconds
setTimeout(function() {
randomizeOnCooldown = false;
randomizeButton.style.opacity = '1';
randomizeButton.style.cursor = 'pointer';
}, 1500);
});
buttonsSection.appendChild(randomizeButton);
// Copy Link button
var copyLinkButton = document.createElement('button');
copyLinkButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/9/91/AdvPlateMaker-link.png" alt="Copy Link" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
copyLinkButton.title = 'Copy Link';
copyLinkButton.className = 'apm-button apm-button-copy';
copyLinkButton.addEventListener('click', function() {
var url = generateShareableUrl();
// Copy to clipboard
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(function() {
copyLinkButton.title = 'Copied!';
setTimeout(function() {
copyLinkButton.title = 'Copy Link';
}, 2000);
}).catch(function(err) {
console.error('Failed to copy URL: ', err);
});
} else {
// Fallback for older browsers
console.warn('Clipboard API not available');
}
});
buttonsSection.appendChild(copyLinkButton);
// Copy Image button
var copyImageButton = document.createElement('button');
copyImageButton.className = 'apm-button apm-button-copy-image';
copyImageButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/c/c5/AdvPlateMaker-image.png" alt="Copy Image" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
copyImageButton.title = 'Copy composite image to clipboard';
copyImageButton.addEventListener('click', function(event) {
// Check if clipboard API is available
if (navigator.clipboard && navigator.clipboard.write) {
copyCompositeImageToClipboard(event.target.closest('button'));
} else {
// Fallback to download
downloadCompositeImage(event.target.closest('button'));
}
});
buttonsSection.appendChild(copyImageButton);
// Wrapper for tabber and buttons
var tabberAndButtonsWrapper = document.createElement('div');
tabberAndButtonsWrapper.style.cssText = 'margin-bottom: 0; display: flex; align-items: flex-start; max-width: 100%; gap: 10px; overflow-x: auto;';
tabberAndButtonsWrapper.appendChild(tabberWrapper);
tabberAndButtonsWrapper.appendChild(buttonsSection);
dropdownContainer.appendChild(tabberAndButtonsWrapper);
container.appendChild(dropdownContainer);
// Image container below dropdowns
var imageContainer = document.createElement('div');
imageContainer.id = 'selected-image-container';
imageContainer.style.cssText = 'text-align: center;';
container.appendChild(imageContainer);
// Acquisition display section (moved below image)
var acquisitionSection = document.createElement('div');
acquisitionSection.className = 'apm-acquisition-section';
var acquisitionTitle = document.createElement('h3');
acquisitionTitle.textContent = 'Acquisition Information';
acquisitionTitle.style.cssText = 'margin-top: 0; margin-bottom: 10px;';
acquisitionSection.appendChild(acquisitionTitle);
var wipNotice = document.createElement('div');
wipNotice.textContent = 'This section is still a work in progress';
wipNotice.style.cssText = 'margin-bottom: 10px; font-style: italic; color: #666;';
acquisitionSection.appendChild(wipNotice);
var acquisitionDisplay = document.createElement('div');
acquisitionDisplay.id = 'acquisition-display';
acquisitionDisplay.style.cssText = 'min-height: 30px;';
acquisitionSection.appendChild(acquisitionDisplay);
container.appendChild(acquisitionSection);
// Insert the container after the content
var contentDiv = document.getElementById('mw-content-text');
if (contentDiv) {
contentDiv.appendChild(container);
}
// Store preset dropdown reference for later population
dropdowns.presetDropdown = advPlatePresetDropdown;
dropdowns.portraitPresetDropdown = portraitPresetDropdown;
return dropdowns;
}
// Fetch images from category using MediaWiki API
function fetchImagesFromCategory(categoryName) {
var api = new mw.Api();
return api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + categoryName,
cmtype: 'file',
cmlimit: 500,
format: 'json'
}).then(function(data) {
if (data.query && data.query.categorymembers) {
return data.query.categorymembers;
}
return [];
});
}
// Auto-crop canvas to remove blank space
function autoCropCanvas(sourceCanvas, padding) {
padding = padding || 10; // Default 10px padding
var ctx = sourceCanvas.getContext('2d');
var width = sourceCanvas.width;
var height = sourceCanvas.height;
var imageData = ctx.getImageData(0, 0, width, height);
var pixels = imageData.data;
// Helper: Check if row has any opaque pixels
function isRowEmpty(y) {
for (var x = 0; x < width; x++) {
var index = (y * width + x) * 4 + 3; // Alpha channel
if (pixels[index] > 10) return false; // Threshold to ignore nearly-transparent
}
return true;
}
// Helper: Check if column has any opaque pixels
function isColumnEmpty(x) {
for (var y = 0; y < height; y++) {
var index = (y * width + x) * 4 + 3; // Alpha channel
if (pixels[index] > 10) return false;
}
return true;
}
// Find top bound
var top = 0;
while (top < height && isRowEmpty(top)) {
top++;
}
// Find bottom bound
var bottom = height - 1;
while (bottom >= top && isRowEmpty(bottom)) {
bottom--;
}
// Find left bound
var left = 0;
while (left < width && isColumnEmpty(left)) {
left++;
}
// Find right bound
var right = width - 1;
while (right >= left && isColumnEmpty(right)) {
right--;
}
// Handle completely transparent image
if (top >= height || left >= width) {
console.warn('No content found in image, returning original canvas');
return sourceCanvas;
}
// Apply padding (with bounds checking)
var paddedTop = Math.max(0, top - padding);
var paddedBottom = Math.min(height - 1, bottom + padding);
var paddedLeft = Math.max(0, left - padding);
var paddedRight = Math.min(width - 1, right + padding);
// Calculate crop dimensions with padding
var cropWidth = paddedRight - paddedLeft + 1;
var cropHeight = paddedBottom - paddedTop + 1;
console.log('Auto-crop: ' + width + 'x' + height + ' -> ' + cropWidth + 'x' + cropHeight);
// Create cropped canvas
var croppedCanvas = document.createElement('canvas');
croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;
var croppedCtx = croppedCanvas.getContext('2d');
// Draw cropped portion
croppedCtx.drawImage(
sourceCanvas,
paddedLeft, paddedTop, cropWidth, cropHeight, // Source
0, 0, cropWidth, cropHeight // Destination
);
return croppedCanvas;
}
// Copy composite image to clipboard
function copyCompositeImageToClipboard(button) {
console.log('Copy image clicked, html2canvas available:', typeof window.html2canvas !== 'undefined');
var imageContainer = document.getElementById('selected-image-container');
// Check if there's an image to copy
if (!imageContainer || imageContainer.children.length === 0) {
alert('No image to copy. Please select some elements first.');
return;
}
// Show loading state
var originalHTML = button.innerHTML;
button.innerHTML = '<span style="font-size: 12px;">...</span>';
button.disabled = true;
// Check if html2canvas is loaded
if (typeof window.html2canvas === 'undefined') {
alert('Image copy feature is still loading. Please try again in a moment.');
button.innerHTML = originalHTML;
button.disabled = false;
return;
}
// Use html2canvas to convert the composite to canvas
window.html2canvas(imageContainer, {
backgroundColor: null, // Transparent background
scale: 1, // 1x scale for 1280×720 output
logging: false,
useCORS: true, // Handle cross-origin images
allowTaint: true
}).then(function(canvas) {
// Auto-crop to remove blank space with 10px padding
var croppedCanvas = autoCropCanvas(canvas, 10);
// Convert cropped canvas to blob
croppedCanvas.toBlob(function(blob) {
if (blob) {
// Copy to clipboard
navigator.clipboard.write([
new ClipboardItem({
'image/png': blob
})
]).then(function() {
// Success feedback
button.innerHTML = '<span style="font-size: 12px;">✓</span>';
setTimeout(function() {
button.innerHTML = originalHTML;
button.disabled = false;
}, 2000);
}).catch(function(err) {
console.error('Failed to copy image:', err);
alert('Failed to copy image. Try the download fallback or check browser permissions.');
button.innerHTML = originalHTML;
button.disabled = false;
});
} else {
alert('Failed to create image.');
button.innerHTML = originalHTML;
button.disabled = false;
}
}, 'image/png');
}).catch(function(err) {
console.error('Failed to render image:', err);
alert('Failed to render image. This may be due to cross-origin restrictions.');
button.innerHTML = originalHTML;
button.disabled = false;
});
}
// Download composite image as fallback
function downloadCompositeImage(button) {
var imageContainer = document.getElementById('selected-image-container');
// Check if there's an image to download
if (!imageContainer || imageContainer.children.length === 0) {
alert('No image to download. Please select some elements first.');
return;
}
// Show loading state
var originalHTML = button.innerHTML;
button.innerHTML = '<span style="font-size: 12px;">...</span>';
button.disabled = true;
// Check if html2canvas is loaded
if (typeof window.html2canvas === 'undefined') {
alert('Image download feature is still loading. Please try again in a moment.');
button.innerHTML = originalHTML;
button.disabled = false;
return;
}
// Use html2canvas to convert the composite to canvas
window.html2canvas(imageContainer, {
backgroundColor: null,
scale: 1, // 1x scale for 1280×720 output
logging: false,
useCORS: true,
allowTaint: true
}).then(function(canvas) {
// Auto-crop to remove blank space with 10px padding
var croppedCanvas = autoCropCanvas(canvas, 10);
croppedCanvas.toBlob(function(blob) {
if (blob) {
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'adventurer-plate.png';
a.click();
URL.revokeObjectURL(url);
// Success feedback
button.innerHTML = '<span style="font-size: 12px;">✓</span>';
setTimeout(function() {
button.innerHTML = originalHTML;
button.disabled = false;
}, 2000);
} else {
alert('Failed to create image.');
button.innerHTML = originalHTML;
button.disabled = false;
}
}, 'image/png');
}).catch(function(err) {
console.error('Failed to render image:', err);
alert('Failed to render image.');
button.innerHTML = originalHTML;
button.disabled = false;
});
}
// Get image info including URL
function getImageInfo(filename) {
var api = new mw.Api();
return api.get({
action: 'query',
titles: filename,
prop: 'imageinfo',
iiprop: 'url',
iiurlwidth: 2560,
format: 'json'
}).then(function(data) {
var pages = data.query.pages;
var pageId = Object.keys(pages)[0];
if (pages[pageId].imageinfo && pages[pageId].imageinfo.length > 0) {
return pages[pageId].imageinfo[0];
}
return null;
});
}
// Update the composite display with all images
function updateCompositeDisplay() {
var imageContainer = document.getElementById('selected-image-container');
imageContainer.innerHTML = '';
var hasAnyImage = false;
categories.forEach(function(cat) {
if (currentImages[cat.key]) {
hasAnyImage = true;
}
});
// Always show if any image is selected OR if we need to show the default blank backing
if (!hasAnyImage && !currentImages['category6']) {
return;
}
// Calculate responsive width based on container (min 300px, max 1280px)
var imageContainer = document.getElementById('selected-image-container');
var containerWidth = imageContainer ? imageContainer.offsetWidth : 1280;
var targetWidth = Math.min(Math.max(containerWidth - 60, 300), 1280); // Leave 60px total margin
var scaleFactor = targetWidth / 2560; // Calculate scale factor from original 2560px width
var targetHeight = 1440 * scaleFactor;
// Create wrapper for overlay effect - scaled responsively
var wrapper = document.createElement('div');
wrapper.style.cssText = 'position: relative; display: inline-block; line-height: 0; width: ' + targetWidth + 'px; height: ' + targetHeight + 'px;';
// Add default backing if category6 (Adventurer Plate Backings) is not selected
if (!currentImages['category6']) {
var defaultBacking = document.createElement('img');
defaultBacking.src = 'http://ffxiv.consolegameswiki.com/mediawiki/images/4/44/AP_blank.png';
defaultBacking.alt = 'Default Backing';
defaultBacking.style.cssText = 'display: block; width: ' + targetWidth + 'px; height: ' + targetHeight + 'px;';
wrapper.appendChild(defaultBacking);
}
// Add images in order (category6 first as base, then overlay subsequent categories)
categories.forEach(function(cat, index) {
if (currentImages[cat.key]) {
var img = document.createElement('img');
img.src = currentImages[cat.key].thumburl || currentImages[cat.key].url;
img.alt = cat.label + ' Image';
// Center Base Plates, Pattern Overlays, Frames, Portrait Frames, Portrait categories, Top Borders, Bottom Borders, and Accents on the Backings image
if (cat.key === 'category4' || cat.key === 'category5' || cat.key === 'category10' || cat.key === 'category9' || cat.key === 'category1' || cat.key === 'category2' || cat.key === 'category3' || cat.key === 'category7' || cat.key === 'category8' || cat.key === 'category11') {
// Calculate backing dimensions - normalize backing to 2560×1440 if it's smaller
var backingWidth = targetWidth; // Always use normalized target size for backing
var backingHeight = targetHeight;
// For overlays, use actual dimensions scaled normally
var imgWidth = (currentImages[cat.key].loadedWidth || 1480) * scaleFactor;
var imgHeight = (currentImages[cat.key].loadedHeight || 840) * scaleFactor;
var leftOffset = (backingWidth - imgWidth) / 2;
var topOffset = (backingHeight - imgHeight) / 2;
// Apply manual adjustment for Adventurer Plate Frames (shift up 28px, scaled)
if (cat.key === 'category10') {
topOffset -= 28 * scaleFactor;
}
// Apply manual adjustment for Adventurer Plate Top Borders (shift up 420px, scaled)
if (cat.key === 'category7') {
topOffset -= 420 * scaleFactor;
}
// Apply manual adjustment for Adventurer Plate Bottom Borders (shift down 364px, scaled)
if (cat.key === 'category8') {
topOffset += 364 * scaleFactor;
}
// Apply manual adjustment for Portrait categories and Adventurer Plate Portrait Frames/Accents based on orientation
var orientationShift = (portraitOrientation === 'right' ? 420 : -420) * scaleFactor;
if (cat.key === 'category1' || cat.key === 'category2' || cat.key === 'category3') {
leftOffset += orientationShift;
}
if (cat.key === 'category9') {
leftOffset += orientationShift;
}
// Apply manual adjustment for Adventurer Plate Accents
if (cat.key === 'category11') {
var accentShift = (portraitOrientation === 'right' ? 676 : -676) * scaleFactor;
leftOffset += accentShift;
topOffset += 296 * scaleFactor;
}
img.style.cssText = 'position: absolute; top: ' + topOffset + 'px; left: ' + leftOffset + 'px; width: ' + imgWidth + 'px; height: ' + imgHeight + 'px;';
} else if (index === 0 || cat.key === 'category6') {
// First image or Backing (category6) - always display at target size (normalized)
// This ensures 1280×720 backings display the same as 2560×1440 backings
img.style.cssText = 'display: block; width: ' + targetWidth + 'px; height: ' + targetHeight + 'px;';
} else {
// All other images overlaid at top-left - scaled normally
var scaledWidth = currentImages[cat.key].loadedWidth * scaleFactor;
var scaledHeight = currentImages[cat.key].loadedHeight * scaleFactor;
img.style.cssText = 'position: absolute; top: 0; left: 0; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;';
}
wrapper.appendChild(img);
}
});
imageContainer.appendChild(wrapper);
}
// Load and display image for a category
function loadImage(filename, categoryKey) {
getImageInfo(filename).then(function(imageInfo) {
if (imageInfo) {
imageInfo.filename = filename.replace('File:', '');
// Store width and height information
var img = new Image();
img.onload = function() {
imageInfo.loadedWidth = this.width;
imageInfo.loadedHeight = this.height;
currentImages[categoryKey] = imageInfo;
updateCompositeDisplay();
};
img.src = imageInfo.thumburl || imageInfo.url;
} else {
console.error('Error loading image:', filename);
}
}).catch(function(error) {
console.error('Error fetching image info:', error);
});
}
// Populate dropdown with images
function populateDropdown(dropdown, images, cat) {
dropdown.innerHTML = '';
if (images.length === 0) {
var noImagesOption = document.createElement('option');
noImagesOption.textContent = 'No images found in category';
noImagesOption.value = '';
dropdown.appendChild(noImagesOption);
return;
}
var defaultOption = document.createElement('option');
defaultOption.textContent = '-- Select an image --';
defaultOption.value = '';
dropdown.appendChild(defaultOption);
images.forEach(function(image) {
var option = document.createElement('option');
option.value = image.title;
var displayName = image.title.replace('File:', '');
// Remove the suffix if present
if (cat.suffix && displayName.endsWith(cat.suffix)) {
displayName = displayName.substring(0, displayName.length - cat.suffix.length);
}
option.textContent = displayName;
dropdown.appendChild(option);
});
dropdown.addEventListener('change', function() {
if (this.value) {
loadImage(this.value, cat.key);
currentSelections[cat.key] = this.value;
saveSelections(currentSelections);
// Check if this is an Adventurer Plate element (categories 4-11)
var advPlateCategories = ['category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
if (advPlateCategories.indexOf(cat.key) !== -1) {
// Set preset dropdown to "Custom"
var presetDropdown = document.getElementById('adventurer-plate-preset-dropdown');
if (presetDropdown) {
presetDropdown.value = 'custom';
}
}
// Check if this is a Portrait element (categories 1-3)
var portraitCategories = ['category1', 'category2', 'category3'];
if (portraitCategories.indexOf(cat.key) !== -1) {
// Set portrait preset dropdown to "Custom"
var portraitPresetDropdown = document.getElementById('portrait-preset-dropdown');
if (portraitPresetDropdown) {
portraitPresetDropdown.value = 'custom';
}
}
// Update acquisition display for all categories (portrait and adventurer plate)
var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
if (allCategories.indexOf(cat.key) !== -1) {
updateAcquisitionDisplay();
}
} else {
currentImages[cat.key] = null;
delete currentSelections[cat.key];
saveSelections(currentSelections);
updateCompositeDisplay();
// Update acquisition display when clearing any category
var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
if (allCategories.indexOf(cat.key) !== -1) {
updateAcquisitionDisplay();
}
}
});
// Restore saved selection if it exists
if (currentSelections[cat.key]) {
dropdown.value = currentSelections[cat.key];
// Trigger load for saved selection
if (dropdown.value) {
loadImage(dropdown.value, cat.key);
}
}
}
// Initialize the gadget
function init() {
// Load html2canvas library for copy image functionality
var script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
script.crossOrigin = 'anonymous';
// Wait for script to load before creating UI
script.onload = function() {
console.log('html2canvas loaded successfully');
initializeGadget();
};
// Fallback if script fails to load
script.onerror = function() {
console.warn('html2canvas failed to load, copy image feature will be disabled');
initializeGadget();
};
document.head.appendChild(script);
}
// Initialize gadget UI and functionality
function initializeGadget() {
var dropdowns = createUI();
// Load presets and acquisition data
Promise.all([loadPresets(), loadAcquisitionData()]).then(function() {
// Populate the preset dropdowns
populatePresetDropdown(dropdowns.presetDropdown, 'adventurerPlate', dropdowns);
populatePresetDropdown(dropdowns.portraitPresetDropdown, 'portrait', dropdowns);
// Then load images for all categories
var loadPromises = categories.map(function(cat) {
return fetchImagesFromCategory(cat.name).then(function(images) {
populateDropdown(dropdowns[cat.key], images, cat);
}).catch(function(error) {
console.error('Error fetching ' + cat.label + ' images:', error);
dropdowns[cat.key].innerHTML = '<option>Error loading images</option>';
});
});
// After all dropdowns are populated, check for URL parameters
Promise.all(loadPromises).then(function() {
setTimeout(function() {
loadFromUrl(dropdowns);
updateAcquisitionDisplay(); // Update acquisition display after loading from URL
}, 500); // Small delay to ensure dropdowns are fully populated
});
});
} // End of initializeGadget function
// Wait for MediaWiki to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
mw.loader.using(['mediawiki.api'], init);
});
} else {
mw.loader.using(['mediawiki.api'], init);
}
})();