Release 0.9.0

This commit is contained in:
2025-11-21 07:26:02 +01:00
committed by ecv
commit 472f0812e7
240 changed files with 20033 additions and 0 deletions

View 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);
});
};

View 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

View File

@@ -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];
}
}