What a mess
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user