Release 0.9.0
This commit is contained in:
172
bot/modules/locations/templates/webmap/location_actions.html
Normal file
172
bot/modules/locations/templates/webmap/location_actions.html
Normal file
@@ -0,0 +1,172 @@
|
||||
// ========================================
|
||||
// Location Popup Actions
|
||||
// ========================================
|
||||
|
||||
// Edit location from map popup
|
||||
window.editLocationFromMap = function(dataset, owner, identifier) {
|
||||
// This follows the exact same pattern as control_edit_link.html
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['toggle_locations_widget_view', {
|
||||
'dom_element_owner': owner,
|
||||
'dom_element_identifier': identifier,
|
||||
'dom_element_origin': dataset,
|
||||
'action': 'edit_location_entry'
|
||||
}]]
|
||||
);
|
||||
console.log('[MAP] Opening edit view for location:', identifier);
|
||||
};
|
||||
|
||||
// Toggle enabled status from map popup
|
||||
window.toggleLocationEnabled = function(dataset, owner, identifier, isChecked) {
|
||||
// This follows the exact same pattern as control_enabled_link.html
|
||||
const action = isChecked ? 'enable_location_entry' : 'disable_location_entry';
|
||||
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['toggle_enabled_flag', {
|
||||
'dom_element_owner': owner,
|
||||
'dom_element_identifier': identifier,
|
||||
'dom_element_origin': dataset,
|
||||
'action': action
|
||||
}]]
|
||||
);
|
||||
console.log('[MAP] Toggling location enabled status:', identifier, 'to', isChecked);
|
||||
};
|
||||
|
||||
// Move location to new position (relative teleport)
|
||||
window.moveLocationFromMap = function(locationId) {
|
||||
const loc = locations[locationId];
|
||||
if (!loc) {
|
||||
console.error('[MAP] Location not found:', locationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open popups
|
||||
map.closePopup();
|
||||
|
||||
// Create info message
|
||||
const infoDiv = L.DomUtil.create('div', 'coordinates-display');
|
||||
infoDiv.style.bottom = '50px';
|
||||
infoDiv.style.background = 'rgba(102, 204, 255, 0.95)';
|
||||
infoDiv.style.borderColor = 'var(--lcars-anakiwa)';
|
||||
infoDiv.style.color = '#000';
|
||||
infoDiv.style.fontWeight = 'bold';
|
||||
infoDiv.innerHTML = '📍 Click new location position on map';
|
||||
document.getElementById('map').appendChild(infoDiv);
|
||||
|
||||
// Change cursor
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Wait for click
|
||||
map.once('click', function(e) {
|
||||
const newCoords = e.latlng;
|
||||
const newX = Math.round(newCoords.lat);
|
||||
const newZ = Math.round(newCoords.lng);
|
||||
|
||||
// Calculate offset
|
||||
const offsetX = newX - loc.coordinates.x;
|
||||
const offsetZ = newZ - loc.coordinates.z;
|
||||
|
||||
// Move teleport_entry relatively if it exists
|
||||
let newTeleportEntry = loc.teleport_entry || {};
|
||||
if (newTeleportEntry.x !== undefined && newTeleportEntry.y !== undefined && newTeleportEntry.z !== undefined) {
|
||||
newTeleportEntry = {
|
||||
x: (parseFloat(newTeleportEntry.x) || 0) + offsetX,
|
||||
y: parseFloat(newTeleportEntry.y) || 0, // Y stays same
|
||||
z: (parseFloat(newTeleportEntry.z) || 0) + offsetZ
|
||||
};
|
||||
}
|
||||
|
||||
// Call edit_location with ALL fields
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['edit_location', {
|
||||
'location_identifier': loc.identifier,
|
||||
'location_name': loc.name,
|
||||
'location_shape': loc.shape,
|
||||
'location_type': loc.type || [],
|
||||
'location_coordinates': {
|
||||
'x': newX,
|
||||
'y': loc.coordinates.y,
|
||||
'z': newZ
|
||||
},
|
||||
'location_teleport_entry': newTeleportEntry,
|
||||
'location_dimensions': loc.dimensions || {},
|
||||
'location_owner': loc.owner,
|
||||
'is_enabled': loc.is_enabled
|
||||
}]]
|
||||
);
|
||||
|
||||
console.log('[MAP] Moved location to:', newX, newZ, 'Offset:', offsetX, offsetZ);
|
||||
|
||||
// Cleanup
|
||||
map.getContainer().style.cursor = '';
|
||||
document.getElementById('map').removeChild(infoDiv);
|
||||
});
|
||||
};
|
||||
|
||||
// Set teleport coordinates
|
||||
window.setTeleportFromMap = function(locationId) {
|
||||
const loc = locations[locationId];
|
||||
if (!loc) {
|
||||
console.error('[MAP] Location not found:', locationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open popups
|
||||
map.closePopup();
|
||||
|
||||
// Create info message
|
||||
const infoDiv = L.DomUtil.create('div', 'coordinates-display');
|
||||
infoDiv.style.bottom = '50px';
|
||||
infoDiv.style.background = 'rgba(255, 153, 0, 0.95)';
|
||||
infoDiv.style.borderColor = 'var(--lcars-golden-tanoi)';
|
||||
infoDiv.style.color = '#000';
|
||||
infoDiv.style.fontWeight = 'bold';
|
||||
infoDiv.innerHTML = '🎯 Click teleport destination on map';
|
||||
document.getElementById('map').appendChild(infoDiv);
|
||||
|
||||
// Change cursor
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
|
||||
// Wait for click
|
||||
map.once('click', function(e) {
|
||||
const teleportCoords = e.latlng;
|
||||
const tpX = Math.round(teleportCoords.lat);
|
||||
const tpZ = Math.round(teleportCoords.lng);
|
||||
|
||||
// Use current Y coordinate or default to location Y
|
||||
const tpY = (loc.teleport_entry && loc.teleport_entry.y) || loc.coordinates.y;
|
||||
|
||||
// Call edit_location with ALL fields
|
||||
window.socket.emit(
|
||||
'widget_event',
|
||||
['locations',
|
||||
['edit_location', {
|
||||
'location_identifier': loc.identifier,
|
||||
'location_name': loc.name,
|
||||
'location_shape': loc.shape,
|
||||
'location_type': loc.type || [],
|
||||
'location_coordinates': loc.coordinates,
|
||||
'location_teleport_entry': {
|
||||
'x': tpX,
|
||||
'y': tpY,
|
||||
'z': tpZ
|
||||
},
|
||||
'location_dimensions': loc.dimensions || {},
|
||||
'location_owner': loc.owner,
|
||||
'is_enabled': loc.is_enabled
|
||||
}]]
|
||||
);
|
||||
|
||||
console.log('[MAP] Set teleport to:', tpX, tpY, tpZ);
|
||||
|
||||
// Cleanup
|
||||
map.getContainer().style.cursor = '';
|
||||
document.getElementById('map').removeChild(infoDiv);
|
||||
});
|
||||
};
|
||||
143
bot/modules/locations/templates/webmap/location_shapes.html
Normal file
143
bot/modules/locations/templates/webmap/location_shapes.html
Normal file
@@ -0,0 +1,143 @@
|
||||
// Helper function to create location shape based on type
|
||||
function createLocationShape(locationId, loc) {
|
||||
const coords = loc.coordinates;
|
||||
const centerLatLng = [coords.x, coords.z]; // 7D2D coordinates (x, z)
|
||||
const dims = loc.dimensions || {};
|
||||
const shape = loc.shape || 'circle';
|
||||
const isEnabled = loc.is_enabled;
|
||||
const is3D = (shape === 'box' || shape === 'spherical');
|
||||
|
||||
// Color scheme
|
||||
const fillColor = isEnabled ? '#ff9900' : '#666666';
|
||||
const strokeColor = isEnabled ? '#ffcc00' : '#999999';
|
||||
const fillOpacity = isEnabled ? 0.3 : 0.15;
|
||||
|
||||
let leafletShape;
|
||||
|
||||
if (shape === 'circle') {
|
||||
const radius = parseFloat(dims.radius || 10);
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: radius,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
} else if (shape === 'spherical') {
|
||||
const radius = parseFloat(dims.radius || 10);
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: radius,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity,
|
||||
dashArray: '5, 5' // Dashed to indicate 3D
|
||||
});
|
||||
} else if (shape === 'rectangular') {
|
||||
const width = parseFloat(dims.width || 10);
|
||||
const length = parseFloat(dims.length || 10);
|
||||
// Rectangle bounds: from center, extend width/length in both directions
|
||||
const bounds = [
|
||||
[coords.x - width, coords.z - length],
|
||||
[coords.x + width, coords.z + length]
|
||||
];
|
||||
leafletShape = L.rectangle(bounds, {
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
} else if (shape === 'box') {
|
||||
const width = parseFloat(dims.width || 10);
|
||||
const length = parseFloat(dims.length || 10);
|
||||
const bounds = [
|
||||
[coords.x - width, coords.z - length],
|
||||
[coords.x + width, coords.z + length]
|
||||
];
|
||||
leafletShape = L.rectangle(bounds, {
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity,
|
||||
dashArray: '5, 5' // Dashed to indicate 3D
|
||||
});
|
||||
} else {
|
||||
// Fallback to circle
|
||||
leafletShape = L.circle(centerLatLng, {
|
||||
radius: 10,
|
||||
fillColor: fillColor,
|
||||
color: strokeColor,
|
||||
weight: 2,
|
||||
opacity: 0.8,
|
||||
fillOpacity: fillOpacity
|
||||
});
|
||||
}
|
||||
|
||||
// Build popup content
|
||||
const dimensionText = shape === 'circle' || shape === 'spherical'
|
||||
? `Radius: ${dims.radius || 'N/A'}`
|
||||
: `Width: ${dims.width || 'N/A'}, Length: ${dims.length || 'N/A'}${shape === 'box' ? ', Height: ' + (dims.height || 'N/A') : ''}`;
|
||||
|
||||
// Parse locationId to extract components
|
||||
// Format: {dataset}_{owner}_{identifier}
|
||||
const locationIdParts = locationId.split('_');
|
||||
const dataset = locationIdParts.slice(0, -2).join('_'); // Handle datasets with underscores
|
||||
const owner = locationIdParts[locationIdParts.length - 2];
|
||||
const identifier = locationIdParts[locationIdParts.length - 1];
|
||||
|
||||
const teleportEntry = loc.teleport_entry || {};
|
||||
const hasTeleport = teleportEntry.x !== undefined && teleportEntry.y !== undefined && teleportEntry.z !== undefined;
|
||||
const teleportText = hasTeleport
|
||||
? `TP: ${parseFloat(teleportEntry.x || 0).toFixed(0)}, ${parseFloat(teleportEntry.y || 0).toFixed(0)}, ${parseFloat(teleportEntry.z || 0).toFixed(0)}`
|
||||
: 'TP: Not set';
|
||||
|
||||
// Use template literal for clean HTML
|
||||
const popupContent = `
|
||||
<div style="min-width: 250px; font-family: monospace;">
|
||||
<b style="font-size: 1.1em;">${loc.name}</b>
|
||||
<br><span style="font-size: 0.9em; color: #888;">${is3D ? '🎲 3D' : '⬜ 2D'} - ${shape}</span>
|
||||
<br><hr style="margin: 5px 0; border-color: #333;">
|
||||
<b>Type:</b> ${loc.type && loc.type.length > 0 ? loc.type.join(', ') : 'None'}
|
||||
<br><b>Owner:</b> ${loc.owner}
|
||||
<br><b>Status:</b> ${isEnabled ? '✅ Enabled' : '❌ Disabled'}
|
||||
<br><b>Position:</b> ${coords.x.toFixed(0)}, ${coords.y.toFixed(0)}, ${coords.z.toFixed(0)}
|
||||
<br><b>Dimensions:</b> ${dimensionText}
|
||||
<br><b>${teleportText}</b>
|
||||
<br><hr style="margin: 8px 0; border-color: #333;">
|
||||
<div style="display: flex; gap: 5px; justify-content: space-between; align-items: center; margin-bottom: 5px;">
|
||||
<button onclick="editLocationFromMap('${dataset}', '${owner}', '${identifier}')"
|
||||
style="flex: 1; padding: 6px 12px; background: var(--lcars-hopbush); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||
✏️ Edit</button>
|
||||
<label style="display: flex; align-items: center; gap: 5px; cursor: pointer; white-space: nowrap;">
|
||||
<input type="checkbox"
|
||||
id="enable_${locationId}"
|
||||
${isEnabled ? 'checked' : ''}
|
||||
onchange="toggleLocationEnabled('${dataset}', '${owner}', '${identifier}', this.checked)"
|
||||
style="cursor: pointer; width: 18px; height: 18px;" />
|
||||
<span style="font-weight: bold;">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<button onclick="moveLocationFromMap('${locationId}')"
|
||||
style="flex: 1; padding: 6px 10px; background: var(--lcars-anakiwa); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
|
||||
📍 Move</button>
|
||||
<button onclick="setTeleportFromMap('${locationId}')"
|
||||
style="flex: 1; padding: 6px 10px; background: var(--lcars-golden-tanoi); color: #000; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9em;">
|
||||
🎯 Set TP</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
leafletShape.bindPopup(popupContent);
|
||||
leafletShape.addTo(map);
|
||||
|
||||
return leafletShape;
|
||||
}
|
||||
|
||||
// Location shapes are now loaded dynamically via Socket.IO
|
||||
// Initial loading is handled by location_update events
|
||||
// See location_update_handler.html for shape creation logic
|
||||
@@ -0,0 +1,32 @@
|
||||
// Listen for location updates/additions
|
||||
if (data.data_type === 'location_update' && data.payload && data.payload.location) {
|
||||
const locationId = data.payload.location_id;
|
||||
const loc = data.payload.location;
|
||||
|
||||
// Update locations dictionary with new data
|
||||
locations[locationId] = loc;
|
||||
|
||||
// Remove old shape if exists
|
||||
if (locationShapes[locationId]) {
|
||||
map.removeLayer(locationShapes[locationId]);
|
||||
delete locationShapes[locationId];
|
||||
}
|
||||
|
||||
// Create new shape
|
||||
try {
|
||||
const shape = createLocationShape(locationId, loc);
|
||||
locationShapes[locationId] = shape;
|
||||
} catch (error) {
|
||||
console.error('[MAP] Error updating location shape:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for location removals
|
||||
if (data.data_type === 'location_remove' && data.payload && data.payload.location_id) {
|
||||
const locationId = data.payload.location_id;
|
||||
|
||||
if (locationShapes[locationId]) {
|
||||
map.removeLayer(locationShapes[locationId]);
|
||||
delete locationShapes[locationId];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user