262 lines
7.9 KiB
JavaScript
262 lines
7.9 KiB
JavaScript
// Resource management for Cursebreaker map
|
|
let resourceLayerGroups = {}; // Map: resource name -> L.layerGroup
|
|
let resourceIcons = {}; // Map: resource name -> L.icon
|
|
let resourceData = {}; // Map: resource name -> resource metadata (skill, level, etc.)
|
|
let filterState = {}; // Map: resource name -> boolean (visible)
|
|
|
|
// Load resources from API
|
|
async function loadResources() {
|
|
try {
|
|
console.log('Loading resources from API...');
|
|
const response = await fetch('/api/resources');
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log(`Received ${data.resources.length} resource types`);
|
|
|
|
// Create icons and layer groups for each resource
|
|
for (const group of data.resources) {
|
|
createResourceGroup(group);
|
|
}
|
|
|
|
// Initialize filter UI
|
|
initializeFilterUI();
|
|
|
|
// Restore filter state from localStorage
|
|
restoreFilterState();
|
|
|
|
console.log(`Loaded ${data.resources.length} resource types successfully`);
|
|
} catch (error) {
|
|
console.error('Error loading resources:', error);
|
|
const container = document.getElementById('resource-filters');
|
|
if (container) {
|
|
container.innerHTML = '<p style="color: #ff6b6b;">Failed to load resources. Check console for details.</p>';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create resource group with icon and markers
|
|
function createResourceGroup(group) {
|
|
const config = window.MapConfig;
|
|
|
|
// Create icon definition (cached per resource type)
|
|
const iconUrl = `data:image/webp;base64,${group.icon_base64}`;
|
|
const icon = L.icon({
|
|
iconUrl: iconUrl,
|
|
iconSize: [config.resourceIconSize, config.resourceIconSize],
|
|
iconAnchor: [config.resourceIconSize / 2, config.resourceIconSize / 2],
|
|
popupAnchor: [0, -(config.resourceIconSize / 2)],
|
|
});
|
|
|
|
resourceIcons[group.name] = icon;
|
|
|
|
// Store metadata
|
|
resourceData[group.name] = {
|
|
item_id: group.item_id,
|
|
skill: group.skill,
|
|
level: group.level,
|
|
};
|
|
|
|
// Create layer group for this resource type
|
|
const layerGroup = L.layerGroup();
|
|
|
|
// Add markers for all positions
|
|
for (const pos of group.positions) {
|
|
const marker = L.marker([pos.y, pos.x], {
|
|
icon: icon,
|
|
title: group.name,
|
|
});
|
|
|
|
// Add popup with resource details
|
|
marker.bindPopup(
|
|
`<strong>${group.name}</strong><br/>Position: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`
|
|
);
|
|
|
|
marker.addTo(layerGroup);
|
|
}
|
|
|
|
// Add to map (initially visible)
|
|
layerGroup.addTo(map);
|
|
resourceLayerGroups[group.name] = layerGroup;
|
|
filterState[group.name] = true; // Initially visible
|
|
}
|
|
|
|
// Initialize filter UI with skill grouping
|
|
function initializeFilterUI() {
|
|
const container = document.getElementById('resource-filters');
|
|
if (!container) {
|
|
console.error('resource-filters container not found');
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = ''; // Clear loading text
|
|
|
|
// Group resources by skill
|
|
const skillGroups = {};
|
|
for (const name in resourceLayerGroups) {
|
|
const metadata = resourceData[name];
|
|
if (!skillGroups[metadata.skill]) {
|
|
skillGroups[metadata.skill] = [];
|
|
}
|
|
skillGroups[metadata.skill].push({
|
|
name: name,
|
|
level: metadata.level,
|
|
});
|
|
}
|
|
|
|
// Sort skills alphabetically
|
|
const sortedSkills = Object.keys(skillGroups).sort();
|
|
|
|
// Create UI for each skill group
|
|
for (const skill of sortedSkills) {
|
|
const skillDiv = document.createElement('div');
|
|
skillDiv.className = 'skill-group';
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'skill-header';
|
|
// Capitalize first letter of skill
|
|
header.textContent = skill.charAt(0).toUpperCase() + skill.slice(1);
|
|
skillDiv.appendChild(header);
|
|
|
|
// Resources are already sorted by level in backend, but sort again to be sure
|
|
skillGroups[skill].sort((a, b) => a.level - b.level);
|
|
|
|
// Create checkbox for each resource
|
|
for (const resource of skillGroups[skill]) {
|
|
const label = createFilterLabel(resource.name);
|
|
skillDiv.appendChild(label);
|
|
}
|
|
|
|
container.appendChild(skillDiv);
|
|
}
|
|
|
|
// Attach bulk filter handlers
|
|
const selectAllBtn = document.getElementById('select-all-resources');
|
|
const deselectAllBtn = document.getElementById('deselect-all-resources');
|
|
|
|
if (selectAllBtn) {
|
|
selectAllBtn.addEventListener('click', () => {
|
|
setAllFilters(true);
|
|
});
|
|
}
|
|
|
|
if (deselectAllBtn) {
|
|
deselectAllBtn.addEventListener('click', () => {
|
|
setAllFilters(false);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create filter label with checkbox and icon
|
|
function createFilterLabel(resourceName) {
|
|
const label = document.createElement('label');
|
|
label.className = 'filter-label';
|
|
|
|
const checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
checkbox.checked = filterState[resourceName];
|
|
checkbox.dataset.resource = resourceName;
|
|
checkbox.addEventListener('change', handleFilterChange);
|
|
|
|
const icon = document.createElement('img');
|
|
icon.src = resourceIcons[resourceName].options.iconUrl;
|
|
icon.className = 'filter-icon';
|
|
icon.alt = resourceName;
|
|
|
|
const text = document.createElement('span');
|
|
text.textContent = resourceName;
|
|
|
|
label.appendChild(checkbox);
|
|
label.appendChild(icon);
|
|
label.appendChild(text);
|
|
|
|
return label;
|
|
}
|
|
|
|
// Handle filter checkbox change
|
|
function handleFilterChange(event) {
|
|
const resourceName = event.target.dataset.resource;
|
|
const isVisible = event.target.checked;
|
|
|
|
filterState[resourceName] = isVisible;
|
|
|
|
// Show/hide layer group
|
|
const layerGroup = resourceLayerGroups[resourceName];
|
|
if (isVisible) {
|
|
layerGroup.addTo(map);
|
|
} else {
|
|
map.removeLayer(layerGroup);
|
|
}
|
|
|
|
// Persist state
|
|
saveFilterState();
|
|
}
|
|
|
|
// Set all filters to visible or hidden
|
|
function setAllFilters(visible) {
|
|
for (const name in filterState) {
|
|
filterState[name] = visible;
|
|
const layerGroup = resourceLayerGroups[name];
|
|
|
|
if (visible) {
|
|
layerGroup.addTo(map);
|
|
} else {
|
|
map.removeLayer(layerGroup);
|
|
}
|
|
}
|
|
|
|
// Update checkboxes
|
|
document.querySelectorAll('#resource-filters input[type="checkbox"]').forEach((cb) => {
|
|
cb.checked = visible;
|
|
});
|
|
|
|
saveFilterState();
|
|
}
|
|
|
|
// Save filter state to localStorage
|
|
function saveFilterState() {
|
|
try {
|
|
localStorage.setItem('cursebreaker_resource_filters', JSON.stringify(filterState));
|
|
} catch (error) {
|
|
console.warn('Failed to save filter state to localStorage:', error);
|
|
}
|
|
}
|
|
|
|
// Restore filter state from localStorage
|
|
function restoreFilterState() {
|
|
const saved = localStorage.getItem('cursebreaker_resource_filters');
|
|
if (!saved) return;
|
|
|
|
try {
|
|
const savedState = JSON.parse(saved);
|
|
|
|
for (const name in savedState) {
|
|
if (resourceLayerGroups[name]) {
|
|
filterState[name] = savedState[name];
|
|
|
|
const layerGroup = resourceLayerGroups[name];
|
|
if (!savedState[name]) {
|
|
map.removeLayer(layerGroup);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update checkboxes after UI is created
|
|
setTimeout(() => {
|
|
document.querySelectorAll('#resource-filters input[type="checkbox"]').forEach((cb) => {
|
|
const name = cb.dataset.resource;
|
|
if (filterState[name] !== undefined) {
|
|
cb.checked = filterState[name];
|
|
}
|
|
});
|
|
}, 100);
|
|
|
|
console.log('Restored filter state from localStorage');
|
|
} catch (error) {
|
|
console.warn('Failed to restore filter state:', error);
|
|
}
|
|
}
|