What a mess

This commit is contained in:
Joan
2025-11-07 15:27:13 +01:00
parent 0b79b3ae59
commit 33cc9586c2
130 changed files with 29819 additions and 1175 deletions

View File

@@ -696,6 +696,11 @@
margin-bottom: 10px;
}
.connection-direction {
font-weight: 500;
color: #ffa726;
}
.connection-item {
background: #0f0f1e;
padding: 10px;
@@ -1068,7 +1073,7 @@
<div id="tab-logs" class="tab-content">
<div class="logs-container">
<div class="logs-header">
<h2>📋 Bot Logs (echoes_of_the_ashes_bot)</h2>
<h2>📋 Bot Logs (echoes_of_the_ashes_api)</h2>
<div class="logs-controls">
<label style="color: #e0e0e0; font-weight: 500;">Lines:</label>
<input type="number" id="logsLineCount" class="logs-lines-input" value="100" min="10" max="1000" step="10">
@@ -1121,6 +1126,83 @@
</div>
</div>
<!-- Select Direction Modal -->
<div class="add-spawn-modal" id="selectDirectionModal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>Select Direction</h3>
<p style="opacity: 0.7; margin-top: 10px;" id="directionModalSubtitle">
From <strong id="fromLocationName"></strong> to <strong id="toLocationName"></strong>
</p>
</div>
<div class="property-group">
<label for="directionSelect">Direction from source:</label>
<select id="directionSelect" style="width: 100%; padding: 10px; border-radius: 6px;
background: #1a1a3e; color: #e0e0e0; border: 2px solid #3a3a6a; font-size: 1em;">
<option value="">-- Select Direction --</option>
<option value="north">⬆️ North</option>
<option value="south">⬇️ South</option>
<option value="east">➡️ East</option>
<option value="west">⬅️ West</option>
<option value="northeast">↗️ Northeast</option>
<option value="northwest">↖️ Northwest</option>
<option value="southeast">↘️ Southeast</option>
<option value="southwest">↙️ Southwest</option>
<option value="up">⬆️ Up</option>
<option value="down">⬇️ Down</option>
<option value="inside">🚪 Inside</option>
<option value="outside">🚪 Outside</option>
</select>
</div>
<div class="property-group" style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="autoReverseConnection" checked style="margin-right: 10px; width: 20px; height: 20px;">
<span>
<strong>Automatically add reverse connection</strong><br>
<span style="opacity: 0.7; font-size: 0.9em;" id="reverseConnectionText">
Will also create a connection back from destination to source
</span>
</span>
</label>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn btn-primary" onclick="confirmAddConnection()">Add Connection</button>
<button class="btn btn-secondary" onclick="closeSelectDirectionModal()">Cancel</button>
</div>
</div>
</div>
<!-- Delete Connection Modal -->
<div class="add-spawn-modal" id="deleteConnectionModal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3 style="color: #f44336;">⚠️ Delete Connection</h3>
<p style="opacity: 0.7; margin-top: 10px;">
<strong id="deleteFromLocationName"></strong><span id="deleteDirectionText"></span><strong id="deleteToLocationName"></strong>
</p>
</div>
<div class="property-group" style="background: #3a2a2a; padding: 15px; border-radius: 8px; border: 2px solid #f44336;">
<p style="margin-bottom: 10px;">⚠️ This will permanently delete this connection.</p>
</div>
<div class="property-group" id="deleteReverseOption" style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="deleteBothConnections" checked style="margin-right: 10px; width: 20px; height: 20px;">
<span>
<strong>Also delete reverse connection</strong><br>
<span style="opacity: 0.7; font-size: 0.9em;" id="deleteReverseText">
Will also delete the connection back
</span>
</span>
</label>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn btn-primary" style="background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);"
onclick="confirmDeleteConnection()">Delete Connection</button>
<button class="btn btn-secondary" onclick="closeDeleteConnectionModal()">Cancel</button>
</div>
</div>
</div>
<!-- Add Interactable Modal -->
<div class="add-spawn-modal" id="addInteractableModal">
<div class="modal-content">

View File

@@ -252,7 +252,7 @@ function populateForm(location) {
renderInteractablesList(location.interactables || {});
// Render connections
renderConnectionsList(location.id, location.exits);
renderConnectionsList(location.id);
}
function updateImagePreview(imagePath) {
@@ -282,26 +282,63 @@ function renderSpawnList(spawns) {
});
}
function renderConnectionsList(locationId, exits) {
const list = document.getElementById('connectionsList');
if (!list) return;
function renderConnectionsList(locationId) {
const list = document.getElementById('connectionList');
if (!list) {
console.error('connectionList element not found!');
return;
}
list.innerHTML = '';
for (const [direction, destId] of Object.entries(exits)) {
const destLocation = currentLocations.find(l => l.id === destId);
// Get connections from the global connections array
const locationConnections = connections.filter(conn => conn.from === locationId);
if (!locationConnections || locationConnections.length === 0) {
list.innerHTML = '<div style="padding: 10px; text-align: center; opacity: 0.5;">No connections</div>';
return;
}
locationConnections.forEach(conn => {
const destLocation = currentLocations.find(l => l.id === conn.to);
if (destLocation) {
const item = document.createElement('div');
item.className = 'connection-item';
item.innerHTML = `
<div class="connection-info">
<div class="connection-direction">${direction}${destLocation.name}</div>
<div class="connection-direction" style="font-weight: 500; color: #ffa726;">
${getDirectionEmoji(conn.direction)} ${conn.direction}
</div>
<div style="font-size: 0.85em; opacity: 0.7; margin-top: 4px;">
${destLocation.name}
</div>
</div>
<button class="btn btn-remove" onclick="deleteConnection('${locationId}', '${destId}')">×</button>
<button class="btn btn-remove" onclick="deleteConnection('${locationId}', '${conn.to}')"
style="width: 32px; height: 32px; padding: 0; font-size: 20px; line-height: 1;">×</button>
`;
list.appendChild(item);
}
}
});
}
function getDirectionEmoji(direction) {
const emojiMap = {
'north': '⬆️',
'south': '⬇️',
'east': '➡️',
'west': '⬅️',
'northeast': '↗️',
'northwest': '↖️',
'southeast': '↘️',
'southwest': '↙️',
'up': '⬆️',
'down': '⬇️',
'inside': '🚪',
'outside': '🚪'
};
return emojiMap[direction.toLowerCase()] || '🔀';
}
// ==================== CANVAS DRAWING ====================
@@ -837,11 +874,103 @@ function filterNPCList() {
});
}
async function promptAddConnection(fromId, toId) {
const direction = prompt('Enter direction (north, south, east, west, northeast, etc.):');
if (!direction) return;
// Store connection data temporarily
let pendingConnection = null;
function promptAddConnection(fromId, toId) {
// Store the connection data
pendingConnection = { fromId, toId };
// Get location names
const fromLocation = currentLocations.find(loc => loc.id === fromId);
const toLocation = currentLocations.find(loc => loc.id === toId);
// Update modal
document.getElementById('fromLocationName').textContent = fromLocation ? fromLocation.name : fromId;
document.getElementById('toLocationName').textContent = toLocation ? toLocation.name : toId;
// Reset selection
document.getElementById('directionSelect').value = '';
document.getElementById('autoReverseConnection').checked = true;
// Close location list modal and open direction modal
closeAddConnectionModal();
document.getElementById('selectDirectionModal').style.display = 'flex';
// Update reverse connection text based on selection
updateReverseConnectionText();
}
function updateReverseConnectionText() {
const directionSelect = document.getElementById('directionSelect');
const reverseText = document.getElementById('reverseConnectionText');
const checkbox = document.getElementById('autoReverseConnection');
if (!directionSelect || !reverseText || !checkbox) return;
const direction = directionSelect.value;
if (!direction) {
reverseText.textContent = 'Will also create a connection back from destination to source';
return;
}
const reverseDirection = getReverseDirection(direction);
const fromLocation = currentLocations.find(loc => loc.id === pendingConnection.fromId);
const toLocation = currentLocations.find(loc => loc.id === pendingConnection.toId);
if (checkbox.checked) {
reverseText.innerHTML = `Will create: <strong>${toLocation?.name || 'destination'}</strong> → ${reverseDirection} → <strong>${fromLocation?.name || 'source'}</strong>`;
} else {
reverseText.textContent = 'Only one-way connection will be created';
}
}
// Listen to direction changes
if (document.getElementById('directionSelect')) {
document.getElementById('directionSelect').addEventListener('change', updateReverseConnectionText);
document.getElementById('autoReverseConnection').addEventListener('change', updateReverseConnectionText);
}
function getReverseDirection(direction) {
const reverseMap = {
'north': 'south',
'south': 'north',
'east': 'west',
'west': 'east',
'northeast': 'southwest',
'northwest': 'southeast',
'southeast': 'northwest',
'southwest': 'northeast',
'up': 'down',
'down': 'up',
'inside': 'outside',
'outside': 'inside'
};
return reverseMap[direction] || direction;
}
function closeSelectDirectionModal() {
document.getElementById('selectDirectionModal').style.display = 'none';
pendingConnection = null;
}
async function confirmAddConnection() {
if (!pendingConnection) return;
const direction = document.getElementById('directionSelect').value;
const autoReverse = document.getElementById('autoReverseConnection').checked;
if (!direction) {
alert('Please select a direction');
return;
}
const { fromId, toId } = pendingConnection;
try {
// Add the main connection
const response = await fetch('/api/editor/connection', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
@@ -850,24 +979,99 @@ async function promptAddConnection(fromId, toId) {
const data = await response.json();
if (data.success) {
showSuccess('Connection added!');
await loadConnections();
await selectLocation(fromId); // Refresh form
drawMap();
closeAddConnectionModal();
} else {
if (!data.success) {
alert('Failed to add connection: ' + data.error);
return;
}
// Add reverse connection if enabled
if (autoReverse) {
const reverseDirection = getReverseDirection(direction);
const reverseResponse = await fetch('/api/editor/connection', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({from: toId, to: fromId, direction: reverseDirection})
});
const reverseData = await reverseResponse.json();
if (reverseData.success) {
showSuccess(`Connection added! (bidirectional: ${direction}${reverseDirection})`);
} else {
showSuccess(`Connection added (${direction}), but reverse failed: ${reverseData.error}`);
}
} else {
showSuccess(`Connection added! (one-way: ${direction})`);
}
// Refresh the view
await loadConnections();
await selectLocation(fromId); // Refresh form
drawMap();
closeSelectDirectionModal();
} catch (error) {
alert('Failed to add connection: ' + error.message);
}
}
async function deleteConnection(fromId, toId) {
if (!confirm('Delete this connection?')) return;
// Store pending deletion data
let pendingDeletion = null;
function deleteConnection(fromId, toId) {
// Get location information
const fromLocation = currentLocations.find(loc => loc.id === fromId);
const toLocation = currentLocations.find(loc => loc.id === toId);
if (!fromLocation || !toLocation) {
alert('Location not found');
return;
}
// Find the direction of this connection
const direction = Object.entries(fromLocation.exits || {}).find(([dir, dest]) => dest === toId)?.[0];
// Check if reverse connection exists
const reverseDirection = getReverseDirection(direction);
const hasReverseConnection = toLocation.exits && toLocation.exits[reverseDirection] === fromId;
// Store deletion data
pendingDeletion = { fromId, toId, direction, hasReverseConnection, reverseDirection };
// Update modal content
document.getElementById('deleteFromLocationName').textContent = fromLocation.name;
document.getElementById('deleteToLocationName').textContent = toLocation.name;
document.getElementById('deleteDirectionText').textContent = direction || 'unknown';
// Show/hide reverse connection option
const reverseOption = document.getElementById('deleteReverseOption');
if (hasReverseConnection) {
reverseOption.style.display = 'block';
document.getElementById('deleteReverseText').innerHTML =
`Also delete: <strong>${toLocation.name}</strong> → ${reverseDirection} → <strong>${fromLocation.name}</strong>`;
document.getElementById('deleteBothConnections').checked = true;
} else {
reverseOption.style.display = 'none';
document.getElementById('deleteBothConnections').checked = false;
}
// Show modal
document.getElementById('deleteConnectionModal').style.display = 'flex';
}
function closeDeleteConnectionModal() {
document.getElementById('deleteConnectionModal').style.display = 'none';
pendingDeletion = null;
}
async function confirmDeleteConnection() {
if (!pendingDeletion) return;
const { fromId, toId, hasReverseConnection, reverseDirection } = pendingDeletion;
const deleteBoth = document.getElementById('deleteBothConnections').checked;
try {
// Delete the main connection
const response = await fetch('/api/editor/connection', {
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
@@ -876,14 +1080,36 @@ async function deleteConnection(fromId, toId) {
const data = await response.json();
if (data.success) {
showSuccess('Connection deleted!');
await loadConnections();
await selectLocation(fromId); // Refresh form
drawMap();
} else {
if (!data.success) {
alert('Failed to delete connection: ' + data.error);
return;
}
// Delete reverse connection if requested and exists
if (deleteBoth && hasReverseConnection) {
const reverseResponse = await fetch('/api/editor/connection', {
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({from: toId, to: fromId})
});
const reverseData = await reverseResponse.json();
if (reverseData.success) {
showSuccess('Both connections deleted!');
} else {
showSuccess(`Connection deleted, but reverse failed: ${reverseData.error}`);
}
} else {
showSuccess('Connection deleted!');
}
// Refresh the view
await loadConnections();
await selectLocation(fromId); // Refresh form
drawMap();
closeDeleteConnectionModal();
} catch (error) {
alert('Failed to delete connection: ' + error.message);
}

View File

@@ -98,7 +98,7 @@ def restart_bot():
# Try to restart the bot container
result = subprocess.run(
['docker', 'restart', 'echoes_of_the_ashes_bot'],
['docker', 'restart', 'echoes_of_the_ashes_api'],
capture_output=True,
text=True,
timeout=30
@@ -149,7 +149,7 @@ def get_bot_logs():
# Get logs from the bot container
result = subprocess.run(
['docker', 'logs', 'echoes_of_the_ashes_bot', '--tail', str(lines)],
['docker', 'logs', 'echoes_of_the_ashes_api', '--tail', str(lines)],
capture_output=True,
text=True,
timeout=10