MediaWiki:Gadget-AdventurerPlateMaker.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';
// 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 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, '_')) + '">' + display + '</a>';
});
text = text.replace(/\[\[([^\]]+)\]\]/g, function(match, page) {
return '<a href="/wiki/' + encodeURIComponent(page.replace(/ /g, '_')) + '">' + 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.style.cssText = 'margin-bottom: 8px; padding: 8px; background-color: #f0f0f0; border-left: 3px solid #2196F3; border-radius: 3px;';
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 style="color: #999; font-style: italic;">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.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; margin-bottom: 10px; display: inline-block;';
// 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;';
// 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);
portraitHeaderRow.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.style.cssText = 'padding: 5px; width: 200px;';
portraitPresetWrapper.appendChild(portraitPresetDropdown);
portraitHeaderRow.appendChild(portraitPresetWrapper);
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;';
// 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.style.cssText = 'padding: 5px; width: 200px;';
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.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: inline-block;';
// 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;';
// 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.style.cssText = 'padding: 5px; width: 200px;';
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;';
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.style.cssText = 'padding: 5px; width: 200px;';
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;';
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.style.cssText = 'padding: 5px; width: 200px;';
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: inline-block; vertical-align: top;';
// 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.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
portraitTabButton.id = 'portrait-tab-button';
var plateTabButton = document.createElement('button');
plateTabButton.textContent = 'Adventurer Plate';
plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
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;';
// Portrait tab content (visible by default)
portraitSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: block; width: 900px; box-sizing: border-box;';
portraitSection.id = 'portrait-tab-content';
// Adventurer Plate tab content (hidden by default)
advPlateSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: none; width: 900px; box-sizing: border-box;';
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.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
});
plateTabButton.addEventListener('click', function() {
document.getElementById('portrait-tab-content').style.display = 'none';
document.getElementById('plate-tab-content').style.display = 'block';
portraitTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
});
// Buttons section (vertical, to the right of tabber) - MOVED HERE BEFORE BUTTON DEFINITIONS
var buttonsSection = document.createElement('div');
buttonsSection.style.cssText = 'display: inline-flex; flex-direction: column; gap: 10px; margin-left: 20px; 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.style.cssText = 'padding: 5px; cursor: pointer; background-color: #f44336; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
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.style.cssText = 'padding: 5px; cursor: pointer; background-color: #4CAF50; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
randomizeButton.addEventListener('click', function() {
// 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;
// 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 !== '') {
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';
}
});
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.style.cssText = 'padding: 5px; cursor: pointer; background-color: #FF9800; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
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);
// Wrapper for tabber and buttons (side by side)
var tabberAndButtonsWrapper = document.createElement('div');
tabberAndButtonsWrapper.style.cssText = 'margin-bottom: 0; display: flex; align-items: flex-start;';
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.style.cssText = 'margin-top: 20px; padding: 15px; background-color: #fff; border: 1px solid #ccc; border-radius: 5px;';
var acquisitionTitle = document.createElement('h3');
acquisitionTitle.textContent = 'Acquisition Information';
acquisitionTitle.style.cssText = 'margin-top: 0; margin-bottom: 10px;';
acquisitionSection.appendChild(acquisitionTitle);
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 [];
});
}
// 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 centering offset dynamically (scaled)
var backingWidth = currentImages['category6'] ? currentImages['category6'].loadedWidth * scaleFactor : targetWidth;
var backingHeight = currentImages['category6'] ? currentImages['category6'].loadedHeight * scaleFactor : targetHeight;
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) {
// First image is the base layer (Backings) - scaled
var scaledWidth = currentImages[cat.key].loadedWidth * scaleFactor;
var scaledHeight = currentImages[cat.key].loadedHeight * scaleFactor;
img.style.cssText = 'display: block; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;';
} else {
// All other images overlaid at top-left - scaled
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() {
// Only show on User:Dr Agon/AdvPlateTest
if (mw.config.get('wgPageName') !== 'User:Dr_Agon/AdvPlateTest') return;
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
});
});
}
// 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);
}
})();