Fix MDI
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||||
import { MudConnection } from '$lib/connection/MudConnection';
|
import { MudConnection } from '$lib/connection/MudConnection';
|
||||||
|
import { ConnectionManager, activeConnections } from '$lib/connection/ConnectionManager';
|
||||||
import { GmcpHandler } from '$lib/gmcp/GmcpHandler';
|
import { GmcpHandler } from '$lib/gmcp/GmcpHandler';
|
||||||
import { TriggerSystem } from '$lib/triggers/TriggerSystem';
|
import { TriggerSystem } from '$lib/triggers/TriggerSystem';
|
||||||
import { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
import { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
||||||
@@ -9,10 +10,12 @@
|
|||||||
connectionStatus,
|
connectionStatus,
|
||||||
activeProfileId,
|
activeProfileId,
|
||||||
activeProfile,
|
activeProfile,
|
||||||
|
profiles,
|
||||||
addToOutputHistory,
|
addToOutputHistory,
|
||||||
updateGmcpData,
|
updateGmcpData,
|
||||||
accessibilitySettings,
|
accessibilitySettings,
|
||||||
uiSettings
|
uiSettings,
|
||||||
|
outputHistory
|
||||||
} from '$lib/stores/mudStore';
|
} from '$lib/stores/mudStore';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
let connection: MudConnection | null = null;
|
let connection: MudConnection | null = null;
|
||||||
|
let connectionManager: ConnectionManager | null = null;
|
||||||
let gmcpHandler: GmcpHandler | null = null;
|
let gmcpHandler: GmcpHandler | null = null;
|
||||||
let triggerSystem: TriggerSystem | null = null;
|
let triggerSystem: TriggerSystem | null = null;
|
||||||
let accessibilityManager: AccessibilityManager | null = null;
|
let accessibilityManager: AccessibilityManager | null = null;
|
||||||
@@ -52,47 +56,95 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unsubscribeFunctions = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
console.log(`MudConnection component mounted for profile: ${profileId}`);
|
||||||
|
|
||||||
// Initialize components
|
// Initialize components
|
||||||
initializeComponents();
|
initializeComponents();
|
||||||
|
|
||||||
// Set up keyboard listener for speech control - use capture phase and make it top-level
|
// Set up keyboard listener for speech control - use capture phase and make it top-level
|
||||||
document.addEventListener('keydown', handleKeyDown, true); // Using document instead of window
|
document.addEventListener('keydown', handleKeyDown, true); // Using document instead of window
|
||||||
|
|
||||||
// Auto-connect if enabled
|
// Get the connection status from the store
|
||||||
if (autoConnect) {
|
const currentStatus = get(connectionStatus)[profileId];
|
||||||
console.log('Auto-connecting profile:', profileId);
|
|
||||||
connect();
|
// Check if we already have a connection in the ConnectionManager
|
||||||
|
if (currentStatus === 'connected' || currentStatus === 'connecting') {
|
||||||
|
console.log(`Connection exists for ${profileId}, getting from manager`);
|
||||||
|
const connectionManager = ConnectionManager.getInstance();
|
||||||
|
const existingConnection = connectionManager.getExistingConnection(profileId);
|
||||||
|
|
||||||
|
if (existingConnection) {
|
||||||
|
console.log(`Found existing connection for ${profileId}, updating local ref`);
|
||||||
|
// Update the connections store
|
||||||
|
connections.update(conns => ({
|
||||||
|
...conns,
|
||||||
|
[profileId]: existingConnection
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update local reference
|
||||||
|
connection = existingConnection;
|
||||||
|
// Re-attach listeners
|
||||||
|
setupConnectionListeners(existingConnection);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Register this connection in the connections store with component instance
|
||||||
|
connections.update(conns => {
|
||||||
|
console.log(`Registering connection component for profile: ${profileId}`);
|
||||||
|
return {
|
||||||
|
...conns,
|
||||||
|
[profileId]: this
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update connection status - ensure UI shows status as 'disconnected' at start
|
||||||
|
if (!currentStatus) {
|
||||||
|
connectionStatus.update(statuses => {
|
||||||
|
console.log(`Setting connection status for ${profileId} to 'disconnected'`);
|
||||||
|
return {
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'disconnected'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update connection status - ensure UI shows status as 'disconnected' at start
|
// Dump the current connection status for debugging
|
||||||
connectionStatus.update(statuses => ({
|
setTimeout(() => {
|
||||||
...statuses,
|
console.log('Current connection status at initialization:', get(connectionStatus));
|
||||||
[profileId]: 'disconnected'
|
}, 100);
|
||||||
}));
|
|
||||||
|
// Auto-connect if enabled and not already connected - with a slight delay
|
||||||
|
if (autoConnect && currentStatus !== 'connected') {
|
||||||
|
console.log('Auto-connecting profile:', profileId);
|
||||||
|
setTimeout(() => connect(), 100);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
console.log(`MudConnection component being destroyed for profile: ${profileId}`);
|
||||||
// Remove keyboard listener
|
// Remove keyboard listener
|
||||||
document.removeEventListener('keydown', handleKeyDown, true); // Match document and capture phase
|
document.removeEventListener('keydown', handleKeyDown, true); // Match document and capture phase
|
||||||
|
|
||||||
// Clean up connection
|
// Don't disconnect the connection - it should persist even when the component is unmounted
|
||||||
if (connection) {
|
// Only remove this component instance from the local connections store (not the ConnectionManager)
|
||||||
connection.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from connections store
|
|
||||||
connections.update(conns => {
|
connections.update(conns => {
|
||||||
const newConns = { ...conns };
|
const newConns = { ...conns };
|
||||||
delete newConns[profileId];
|
delete newConns[profileId];
|
||||||
return newConns;
|
return newConns;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update connection status
|
// Clean up subscriptions
|
||||||
connectionStatus.update(statuses => {
|
unsubscribeFunctions.forEach(unsubFn => {
|
||||||
const newStatuses = { ...statuses };
|
if (typeof unsubFn === 'function') {
|
||||||
delete newStatuses[profileId];
|
try {
|
||||||
return newStatuses;
|
unsubFn();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during unsubscribe:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,6 +152,9 @@
|
|||||||
* Initialize connection components
|
* Initialize connection components
|
||||||
*/
|
*/
|
||||||
function initializeComponents() {
|
function initializeComponents() {
|
||||||
|
// Get singleton instance of ConnectionManager
|
||||||
|
connectionManager = ConnectionManager.getInstance();
|
||||||
|
|
||||||
// Create GMCP handler
|
// Create GMCP handler
|
||||||
gmcpHandler = new GmcpHandler();
|
gmcpHandler = new GmcpHandler();
|
||||||
|
|
||||||
@@ -111,6 +166,47 @@
|
|||||||
|
|
||||||
// Set up event listeners
|
// Set up event listeners
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
|
|
||||||
|
// Get or create connection from ConnectionManager
|
||||||
|
updateConnectionFromStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update local connection reference from store
|
||||||
|
*/
|
||||||
|
function updateConnectionFromStore() {
|
||||||
|
// Subscribe to activeConnections to get updates
|
||||||
|
const unsubscribe = activeConnections.subscribe(connections => {
|
||||||
|
connection = connections[profileId] || null;
|
||||||
|
|
||||||
|
if (connection) {
|
||||||
|
// Re-attach event listeners when connection changes
|
||||||
|
setupConnectionListeners(connection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to unsubscribe functions
|
||||||
|
unsubscribeFunctions.push(unsubscribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up listeners for a specific connection
|
||||||
|
*/
|
||||||
|
function setupConnectionListeners(conn: MudConnection) {
|
||||||
|
// First remove ALL event listeners to prevent duplicates
|
||||||
|
console.log('Setting up connection listeners and removing old ones');
|
||||||
|
conn.off('received');
|
||||||
|
conn.off('sent');
|
||||||
|
conn.off('connected');
|
||||||
|
conn.off('disconnected');
|
||||||
|
conn.off('error');
|
||||||
|
|
||||||
|
// Add new listeners
|
||||||
|
conn.on('received', handleReceived);
|
||||||
|
conn.on('sent', handleSent);
|
||||||
|
conn.on('connected', handleConnected);
|
||||||
|
conn.on('disconnected', handleDisconnected);
|
||||||
|
conn.on('error', handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,6 +260,7 @@
|
|||||||
console.error('Error updating TTS setting:', error);
|
console.error('Error updating TTS setting:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
unsubscribeFunctions.push(unsubscribeTts);
|
||||||
|
|
||||||
// Set up only GMCP debugging subscription
|
// Set up only GMCP debugging subscription
|
||||||
let previousGmcpDebug = $uiSettings.debugGmcp;
|
let previousGmcpDebug = $uiSettings.debugGmcp;
|
||||||
@@ -172,70 +269,82 @@
|
|||||||
previousGmcpDebug = settings.debugGmcp;
|
previousGmcpDebug = settings.debugGmcp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
unsubscribeFunctions.push(unsubscribeUiSettings);
|
||||||
// Clean up subscriptions on component destruction
|
|
||||||
onDestroy(() => {
|
|
||||||
unsubscribeUiSettings();
|
|
||||||
unsubscribeTts();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to the MUD server
|
* Connect to the MUD server
|
||||||
*/
|
*/
|
||||||
export function connect() {
|
export function connect() {
|
||||||
const profile = get(activeProfile);
|
|
||||||
console.log('Connecting to profile:', profile);
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
addToOutputHistory('Error: No active profile selected.');
|
|
||||||
console.error('No active profile selected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update connection status
|
|
||||||
connectionStatus.update(statuses => ({
|
|
||||||
...statuses,
|
|
||||||
[profileId]: 'connecting'
|
|
||||||
}));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
addToOutputHistory(`Connecting to ${profile.host}:${profile.port}...`);
|
// First check if we are already connected
|
||||||
|
const currentStatus = get(connectionStatus)[profileId];
|
||||||
|
if (currentStatus === 'connected' || currentStatus === 'connecting') {
|
||||||
|
console.log(`Already ${currentStatus} for profile ${profileId}, not connecting again`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create connection
|
// Find the profile by ID - don't rely on activeProfile
|
||||||
connection = new MudConnection({
|
const allProfiles = get(profiles);
|
||||||
|
const profile = allProfiles.find(p => p.id === profileId);
|
||||||
|
|
||||||
|
console.log(`Connecting to profile ${profileId}:`, profile);
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
|
addToOutputHistory(`Error: Profile ${profileId} not found.`);
|
||||||
|
console.error(`Profile ${profileId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => {
|
||||||
|
console.log(`Setting connection status for ${profileId} to 'connecting'`);
|
||||||
|
return {
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'connecting'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add to output history if this is the active profile
|
||||||
|
if (get(activeProfileId) === profileId) {
|
||||||
|
addToOutputHistory(`Connecting to ${profile.host}:${profile.port}...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the connection manager to get or create a connection
|
||||||
|
if (!connectionManager) {
|
||||||
|
connectionManager = ConnectionManager.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect using the connection manager
|
||||||
|
connectionManager.connect(profileId, {
|
||||||
host: profile.host,
|
host: profile.host,
|
||||||
port: profile.port,
|
port: profile.port,
|
||||||
useSSL: profile.useSSL,
|
useSSL: profile.useSSL,
|
||||||
gmcpHandler: gmcpHandler || undefined
|
gmcpHandler: gmcpHandler || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Connection object created:', connection);
|
// Update local connection reference
|
||||||
|
setTimeout(() => {
|
||||||
|
updateConnectionFromStore();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// Set up connection event listeners
|
|
||||||
connection.on('connected', handleConnected);
|
|
||||||
connection.on('disconnected', handleDisconnected);
|
|
||||||
connection.on('error', handleError);
|
|
||||||
connection.on('received', handleReceived);
|
|
||||||
connection.on('sent', handleSent);
|
|
||||||
|
|
||||||
// Add to connections store
|
|
||||||
connections.update(conns => ({
|
|
||||||
...conns,
|
|
||||||
[profileId]: connection as MudConnection
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Connect
|
|
||||||
connection.connect();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to connect:', error);
|
console.error(`Failed to connect to ${profileId}:`, error);
|
||||||
|
|
||||||
connectionStatus.update(statuses => ({
|
connectionStatus.update(statuses => {
|
||||||
...statuses,
|
return {
|
||||||
[profileId]: 'error'
|
...statuses,
|
||||||
}));
|
[profileId]: 'error'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addToOutputHistory(`Error connecting to ${profile.host}:${profile.port} - ${error}`);
|
const errorMsg = `Error connecting to profile ${profileId}: ${error}`;
|
||||||
|
console.error(errorMsg);
|
||||||
|
|
||||||
|
// Only add to output history if this is the active profile
|
||||||
|
if (get(activeProfileId) === profileId) {
|
||||||
|
addToOutputHistory(errorMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,23 +352,46 @@
|
|||||||
* Disconnect from the MUD server
|
* Disconnect from the MUD server
|
||||||
*/
|
*/
|
||||||
export function disconnect() {
|
export function disconnect() {
|
||||||
if (connection) {
|
if (!connectionManager) {
|
||||||
connection.disconnect();
|
connectionManager = ConnectionManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionManager.disconnect(profileId);
|
||||||
|
console.log(`Disconnected profile ${profileId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle connection established
|
* Handle connection established
|
||||||
*/
|
*/
|
||||||
function handleConnected() {
|
function handleConnected() {
|
||||||
const profile = get(activeProfile);
|
try {
|
||||||
|
// Find the profile by ID
|
||||||
|
const allProfiles = get(profiles);
|
||||||
|
const profile = allProfiles.find(p => p.id === profileId);
|
||||||
|
|
||||||
connectionStatus.update(statuses => ({
|
console.log(`Profile ${profileId} connected:`, profile);
|
||||||
...statuses,
|
|
||||||
[profileId]: 'connected'
|
|
||||||
}));
|
|
||||||
|
|
||||||
addToOutputHistory(`Connected to ${profile?.host}:${profile?.port}`);
|
// Update connection status GLOBALLY, not just for this profile
|
||||||
|
connectionStatus.update(statuses => {
|
||||||
|
console.log(`Setting connection status for ${profileId} to 'connected'`);
|
||||||
|
console.log('Previous connection statuses:', statuses);
|
||||||
|
const newStatuses = {
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'connected'
|
||||||
|
};
|
||||||
|
console.log('New connection statuses:', newStatuses);
|
||||||
|
return newStatuses;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dump the current connection status for debugging
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Current connection status after connect:', get(connectionStatus));
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Only add to output history if this is the active profile
|
||||||
|
if (get(activeProfileId) === profileId) {
|
||||||
|
addToOutputHistory(`Connected to ${profile?.host}:${profile?.port}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle auto-login if enabled
|
// Handle auto-login if enabled
|
||||||
if (profile?.autoLogin?.enabled) {
|
if (profile?.autoLogin?.enabled) {
|
||||||
@@ -290,18 +422,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch('connected');
|
dispatch('connected');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error handling connection for profile ${profileId}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle connection closed
|
* Handle connection closed
|
||||||
*/
|
*/
|
||||||
function handleDisconnected() {
|
function handleDisconnected() {
|
||||||
connectionStatus.update(statuses => ({
|
console.log(`Profile ${profileId} disconnected`);
|
||||||
...statuses,
|
|
||||||
[profileId]: 'disconnected'
|
connectionStatus.update(statuses => {
|
||||||
}));
|
console.log(`Setting connection status for ${profileId} to 'disconnected'`);
|
||||||
|
return {
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'disconnected'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add to output history if this is the active profile
|
||||||
|
if (get(activeProfileId) === profileId) {
|
||||||
|
addToOutputHistory('Disconnected from server.');
|
||||||
|
}
|
||||||
|
|
||||||
addToOutputHistory('Disconnected from server.');
|
|
||||||
dispatch('disconnected');
|
dispatch('disconnected');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,19 +453,27 @@
|
|||||||
* Handle connection error
|
* Handle connection error
|
||||||
*/
|
*/
|
||||||
function handleError(error: any) {
|
function handleError(error: any) {
|
||||||
connectionStatus.update(statuses => ({
|
console.log(`Profile ${profileId} connection error:`, error);
|
||||||
...statuses,
|
|
||||||
[profileId]: 'error'
|
connectionStatus.update(statuses => {
|
||||||
}));
|
console.log(`Setting connection status for ${profileId} to 'error'`);
|
||||||
|
return {
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'error'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Format the error message for display
|
// Format the error message for display
|
||||||
const errorMessage = typeof error === 'object' ?
|
const errorMessage = typeof error === 'object' ?
|
||||||
(error.message || JSON.stringify(error)) :
|
(error.message || JSON.stringify(error)) :
|
||||||
String(error);
|
String(error);
|
||||||
|
|
||||||
addToOutputHistory(`Connection error: ${errorMessage}`, false, [
|
// Only add to output history if this is the active profile
|
||||||
{ pattern: 'Connection error', color: '#ff5555', isRegex: false }
|
if (get(activeProfileId) === profileId) {
|
||||||
]);
|
addToOutputHistory(`Connection error: ${errorMessage}`, false, [
|
||||||
|
{ pattern: 'Connection error', color: '#ff5555', isRegex: false }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch('error', { error });
|
dispatch('error', { error });
|
||||||
}
|
}
|
||||||
@@ -330,31 +482,69 @@
|
|||||||
* Handle received data
|
* Handle received data
|
||||||
*/
|
*/
|
||||||
function handleReceived(text: string) {
|
function handleReceived(text: string) {
|
||||||
// Add to output history first
|
console.log(`Profile ${profileId} received data, active profile: ${get(activeProfileId)}`);
|
||||||
addToOutputHistory(text);
|
|
||||||
|
|
||||||
// Process triggers with safe error handling
|
try {
|
||||||
if (triggerSystem) {
|
// First check if this is the active profile - only handle messages for the active profile
|
||||||
try {
|
if (get(activeProfileId) !== profileId) {
|
||||||
triggerSystem.processTriggers(text);
|
console.log(`Ignoring received data for inactive profile ${profileId}`);
|
||||||
} catch (error) {
|
return;
|
||||||
console.error('Error processing triggers:', error);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Try to use text-to-speech if enabled
|
// First, ensure this profile has an output history entry
|
||||||
if (accessibilityManager && $accessibilitySettings.textToSpeech) {
|
const history = get(outputHistory);
|
||||||
// Use a small timeout to avoid UI blocking
|
if (!history[profileId] || !Array.isArray(history[profileId])) {
|
||||||
setTimeout(() => {
|
console.log(`Creating fresh output history for profile ${profileId}`);
|
||||||
|
outputHistory.update(h => ({
|
||||||
|
...h,
|
||||||
|
[profileId]: []
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the output item
|
||||||
|
const outputItem = {
|
||||||
|
id: `output-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||||
|
text,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
isInput: false,
|
||||||
|
highlights: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add directly to this profile's history
|
||||||
|
outputHistory.update(history => {
|
||||||
|
console.log(`Adding text to history for profile ${profileId}`);
|
||||||
|
const profileHistory = history[profileId] || [];
|
||||||
|
return {
|
||||||
|
...history,
|
||||||
|
[profileId]: [...profileHistory, outputItem]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process triggers with safe error handling
|
||||||
|
if (triggerSystem) {
|
||||||
try {
|
try {
|
||||||
accessibilityManager.speak(text);
|
triggerSystem.processTriggers(text);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error using text-to-speech:', error);
|
console.error(`Error processing triggers for ${profileId}:`, error);
|
||||||
}
|
}
|
||||||
}, 10);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dispatch('received', { text });
|
// Try to use text-to-speech if enabled
|
||||||
|
if (accessibilityManager && $accessibilitySettings.textToSpeech && get(activeProfileId) === profileId) {
|
||||||
|
// Use a small timeout to avoid UI blocking
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
accessibilityManager.speak(text);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error using text-to-speech:', error);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch('received', { text });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error handling received data for profile ${profileId}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,96 +2,232 @@
|
|||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import MudTerminal from './MudTerminal.svelte';
|
import MudTerminal from './MudTerminal.svelte';
|
||||||
import MudConnection from './MudConnection.svelte';
|
import MudConnection from './MudConnection.svelte';
|
||||||
import { activeProfileId, profiles, connectionStatus, addToOutputHistory } from '$lib/stores/mudStore';
|
import { ConnectionManager } from '$lib/connection/ConnectionManager';
|
||||||
|
import { activeProfileId, profiles, connectionStatus, addToOutputHistory, outputHistory, connections } from '$lib/stores/mudStore';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
||||||
|
|
||||||
|
// Make sure tabs are defined to prevent errors
|
||||||
|
$: safeTabs = tabs || [];
|
||||||
|
$: safeConnectionStatus = $connectionStatus || {};
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
let tabs: { id: string; profile: MudProfile }[] = [];
|
let tabs: { id: string; profile: MudProfile }[] = [];
|
||||||
let activeTab: string | null = null;
|
let activeTab: string | null = null;
|
||||||
let autoConnectOnStart = true; // Auto-connect to the active profile on start
|
let autoConnectOnStart = true; // Auto-connect to the active profile on start
|
||||||
|
|
||||||
// Component references
|
// Handle keyboard navigation in the tab bar
|
||||||
let connections: { [key: string]: any } = {};
|
function handleTabsKeydown(event: KeyboardEvent) {
|
||||||
|
if (!safeTabs || safeTabs.length === 0) return;
|
||||||
|
|
||||||
|
// Get current tab index
|
||||||
|
const currentIndex = safeTabs.findIndex(tab => tab.id === activeTab);
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'ArrowDown':
|
||||||
|
// Move to next tab
|
||||||
|
event.preventDefault();
|
||||||
|
const nextIndex = (currentIndex + 1) % safeTabs.length;
|
||||||
|
changeTab(safeTabs[nextIndex].id);
|
||||||
|
document.getElementById(`tab-${safeTabs[nextIndex].id}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'ArrowUp':
|
||||||
|
// Move to previous tab
|
||||||
|
event.preventDefault();
|
||||||
|
const prevIndex = (currentIndex - 1 + safeTabs.length) % safeTabs.length;
|
||||||
|
changeTab(safeTabs[prevIndex].id);
|
||||||
|
document.getElementById(`tab-${safeTabs[prevIndex].id}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Home':
|
||||||
|
// Move to first tab
|
||||||
|
event.preventDefault();
|
||||||
|
changeTab(safeTabs[0].id);
|
||||||
|
document.getElementById(`tab-${safeTabs[0].id}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
// Move to last tab
|
||||||
|
event.preventDefault();
|
||||||
|
const lastIndex = safeTabs.length - 1;
|
||||||
|
changeTab(safeTabs[lastIndex].id);
|
||||||
|
document.getElementById(`tab-${safeTabs[lastIndex].id}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Let other keys function normally
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize tabs from profiles
|
// Initialize tabs from profiles
|
||||||
function initializeTabs() {
|
function initializeTabs() {
|
||||||
// Get the profiles from the store
|
try {
|
||||||
const allProfiles = $profiles || [];
|
// Get the profiles from the store
|
||||||
console.log('Initializing tabs with profiles:', allProfiles);
|
const allProfiles = $profiles || [];
|
||||||
|
console.log('Initializing tabs with profiles:', allProfiles);
|
||||||
|
|
||||||
if (allProfiles.length === 0) {
|
if (allProfiles.length === 0) {
|
||||||
console.warn('No profiles available to create tabs');
|
console.warn('No profiles available to create tabs');
|
||||||
tabs = [];
|
tabs = [];
|
||||||
// If no tabs are available, we should show a message to create a profile
|
// If no tabs are available, we should show a message to create a profile
|
||||||
addToOutputHistory('No profiles available. Please create a profile in the sidebar.');
|
addToOutputHistory('No profiles available. Please create a profile in the sidebar.');
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
tabs = allProfiles.map(profile => ({
|
|
||||||
id: profile.id,
|
|
||||||
profile
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log('Created tabs:', tabs);
|
|
||||||
|
|
||||||
// Set the active tab from the store or default to the first tab
|
|
||||||
activeTab = $activeProfileId || (tabs.length > 0 ? tabs[0].id : null);
|
|
||||||
console.log('Active tab set to:', activeTab);
|
|
||||||
|
|
||||||
// Update the active profile ID in the store
|
|
||||||
if (activeTab) {
|
|
||||||
activeProfileId.set(activeTab);
|
|
||||||
|
|
||||||
// Auto-connect if enabled
|
|
||||||
if (autoConnectOnStart && tabs.length > 0 && !$connectionStatus[activeTab]) {
|
|
||||||
console.log(`Auto-connecting to tab: ${activeTab}`);
|
|
||||||
setTimeout(() => connectToMud(activeTab), 500);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Force a rerender of the tabs
|
// Create new tabs array with all profile information
|
||||||
setTimeout(() => {
|
const newTabs = allProfiles.map(profile => ({
|
||||||
console.log('Forcing tab rerender');
|
id: profile.id,
|
||||||
tabs = [...tabs];
|
profile
|
||||||
}, 0);
|
}));
|
||||||
|
|
||||||
|
// Update tabs array
|
||||||
|
tabs = newTabs;
|
||||||
|
console.log('Created tabs:', tabs);
|
||||||
|
|
||||||
|
// Set the active tab from the store or default to the first tab
|
||||||
|
const newActiveTab = $activeProfileId || (tabs.length > 0 ? tabs[0].id : null);
|
||||||
|
|
||||||
|
if (newActiveTab !== activeTab) {
|
||||||
|
console.log(`Changing active tab from ${activeTab} to ${newActiveTab}`);
|
||||||
|
activeTab = newActiveTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Active tab set to:', activeTab);
|
||||||
|
|
||||||
|
// Update the active profile ID in the store
|
||||||
|
if (activeTab) {
|
||||||
|
console.log(`Setting active profile ID to ${activeTab}`);
|
||||||
|
activeProfileId.set(activeTab);
|
||||||
|
|
||||||
|
// Auto-connect if enabled
|
||||||
|
if (autoConnectOnStart && tabs.length > 0 && !$connectionStatus[activeTab]) {
|
||||||
|
console.log(`Auto-connecting to tab: ${activeTab}`);
|
||||||
|
setTimeout(() => connectToMud(activeTab), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a rerender of the tabs
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Forcing tab rerender');
|
||||||
|
tabs = [...tabs];
|
||||||
|
}, 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing tabs:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tab changes
|
// Handle tab changes
|
||||||
function changeTab(tabId: string) {
|
function changeTab(tabId: string) {
|
||||||
activeTab = tabId;
|
try {
|
||||||
activeProfileId.set(tabId);
|
console.log(`Changing tab to: ${tabId}`);
|
||||||
|
|
||||||
|
// First, update activeProfileId in the store to trigger reactive updates
|
||||||
|
activeProfileId.set(tabId);
|
||||||
|
|
||||||
|
// Then update the local state
|
||||||
|
activeTab = tabId;
|
||||||
|
|
||||||
|
// Log the connections state for debugging
|
||||||
|
console.log('Current connection status:', $connectionStatus);
|
||||||
|
|
||||||
|
// Make sure the connection store is updated with the active profile
|
||||||
|
const connectionManager = ConnectionManager.getInstance();
|
||||||
|
const existingConnection = connectionManager.getExistingConnection(tabId);
|
||||||
|
|
||||||
|
// Update the connections store - this ensures the connection is associated with the profile ID
|
||||||
|
if (existingConnection) {
|
||||||
|
// Check if we need to ensure event listeners are properly set up
|
||||||
|
const isReused = get(connections)[tabId] === existingConnection;
|
||||||
|
console.log(`Connection is ${isReused ? 'being reused' : 'new'} for tab ${tabId}`);
|
||||||
|
|
||||||
|
connections.update(conns => ({
|
||||||
|
...conns,
|
||||||
|
[tabId]: existingConnection
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we're only rendering the active tab now, we don't need to force updates
|
||||||
|
// to other components, the component itself will be created/destroyed
|
||||||
|
console.log(`Tab change complete - active tab is now: ${activeTab}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error changing tab to ${tabId}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to a MUD server
|
// Connect to a MUD server
|
||||||
function connectToMud(profileId: string) {
|
function connectToMud(profileId: string) {
|
||||||
const connectionComponent = connections[profileId];
|
try {
|
||||||
if (connectionComponent) {
|
console.log(`Attempting to connect to MUD for profile: ${profileId}`);
|
||||||
connectionComponent.connect();
|
|
||||||
|
// Use the ConnectionManager instead of the local connections object
|
||||||
|
const connectionManager = ConnectionManager.getInstance();
|
||||||
|
|
||||||
|
// Find the profile
|
||||||
|
const profile = $profiles.find(p => p.id === profileId);
|
||||||
|
if (!profile) {
|
||||||
|
console.error(`No profile found for ID: ${profileId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect using the connection manager
|
||||||
|
connectionManager.connect(profileId, {
|
||||||
|
host: profile.host,
|
||||||
|
port: profile.port,
|
||||||
|
useSSL: profile.useSSL
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error connecting to MUD for profile ${profileId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from a MUD server
|
// Disconnect from a MUD server
|
||||||
function disconnectFromMud(profileId: string) {
|
function disconnectFromMud(profileId: string) {
|
||||||
const connectionComponent = connections[profileId];
|
try {
|
||||||
if (connectionComponent) {
|
console.log(`Attempting to disconnect from MUD for profile: ${profileId}`);
|
||||||
connectionComponent.disconnect();
|
|
||||||
|
// Use the ConnectionManager
|
||||||
|
const connectionManager = ConnectionManager.getInstance();
|
||||||
|
connectionManager.disconnect(profileId);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error disconnecting from MUD for profile ${profileId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close a tab
|
// Close a tab
|
||||||
function closeTab(profileId: string) {
|
function closeTab(profileId: string) {
|
||||||
// Disconnect if connected
|
try {
|
||||||
if ($connectionStatus[profileId] === 'connected') {
|
console.log(`Attempting to close tab for profile: ${profileId}`);
|
||||||
disconnectFromMud(profileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the tab
|
// Safely check the connection status
|
||||||
tabs = tabs.filter(tab => tab.id !== profileId);
|
const status = $connectionStatus[profileId];
|
||||||
|
console.log(`Current connection status for ${profileId}: ${status}`);
|
||||||
|
|
||||||
// If the closed tab was active, activate another tab
|
// Disconnect if connected
|
||||||
if (activeTab === profileId) {
|
if (status === 'connected') {
|
||||||
activeTab = tabs.length > 0 ? tabs[0].id : null;
|
console.log(`Disconnecting ${profileId} before closing tab`);
|
||||||
activeProfileId.set(activeTab);
|
disconnectFromMud(profileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the tab
|
||||||
|
console.log(`Removing tab for profile ${profileId}`);
|
||||||
|
tabs = tabs.filter(tab => tab.id !== profileId);
|
||||||
|
|
||||||
|
// If the closed tab was active, activate another tab
|
||||||
|
if (activeTab === profileId) {
|
||||||
|
console.log(`Closed tab was active, finding new active tab`);
|
||||||
|
const newActiveTab = tabs.length > 0 ? tabs[0].id : null;
|
||||||
|
console.log(`Setting new active tab to: ${newActiveTab}`);
|
||||||
|
activeTab = newActiveTab;
|
||||||
|
activeProfileId.set(newActiveTab);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error closing tab for profile ${profileId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,13 +255,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update when profiles change
|
// Update when profiles change or active profile changes
|
||||||
$: if ($profiles) {
|
$: if ($profiles) {
|
||||||
console.log('Profiles updated in store, reinitializing tabs:', $profiles);
|
console.log('Profiles updated in store, reinitializing tabs:', $profiles);
|
||||||
initializeTabs();
|
initializeTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When activeProfileId changes externally, ensure activeTab is in sync
|
||||||
|
$: if ($activeProfileId && $activeProfileId !== activeTab) {
|
||||||
|
console.log(`activeProfileId changed to ${$activeProfileId}, updating activeTab to match`);
|
||||||
|
activeTab = $activeProfileId;
|
||||||
|
// Force a UI update
|
||||||
|
setTimeout(() => {
|
||||||
|
tabs = [...tabs];
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
console.log('MudMdi component mounted');
|
||||||
|
|
||||||
|
// Make sure connectionStatus store is initialized
|
||||||
|
if (!$connectionStatus) {
|
||||||
|
connectionStatus.set({});
|
||||||
|
}
|
||||||
|
|
||||||
// Initial setup of tabs
|
// Initial setup of tabs
|
||||||
initializeTabs();
|
initializeTabs();
|
||||||
|
|
||||||
@@ -138,8 +291,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mud-mdi">
|
<div class="mud-mdi">
|
||||||
<div class="mud-mdi-tabs" role="tablist">
|
<div class="mud-mdi-tabs" role="tablist" aria-label="MUD connections" tabindex="0" on:keydown={handleTabsKeydown}>
|
||||||
{#each tabs as tab}
|
{#each safeTabs as tab}
|
||||||
<button
|
<button
|
||||||
class="mud-mdi-tab"
|
class="mud-mdi-tab"
|
||||||
class:active={activeTab === tab.id}
|
class:active={activeTab === tab.id}
|
||||||
@@ -179,13 +332,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#each tabs as tab (tab.id)}
|
<!-- Only render the active tab -->
|
||||||
|
{#each safeTabs.filter(tab => tab.id === activeTab) as tab (tab.id)}
|
||||||
<div
|
<div
|
||||||
class="mud-mdi-pane"
|
class="mud-mdi-pane"
|
||||||
style="display: {activeTab === tab.id ? 'flex' : 'none'}"
|
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
id={`panel-${tab.id}`}
|
id={`panel-${tab.id}`}
|
||||||
aria-labelledby={`tab-${tab.id}`}
|
aria-labelledby={`tab-${tab.id}`}
|
||||||
|
data-tab-id={tab.id}
|
||||||
|
data-active-tab={activeTab}
|
||||||
>
|
>
|
||||||
<div class="mud-mdi-pane-header">
|
<div class="mud-mdi-pane-header">
|
||||||
<div class="mud-mdi-pane-title">
|
<div class="mud-mdi-pane-title">
|
||||||
@@ -226,7 +381,6 @@
|
|||||||
|
|
||||||
<MudConnection
|
<MudConnection
|
||||||
profileId={tab.id}
|
profileId={tab.id}
|
||||||
bind:this={connections[tab.id]}
|
|
||||||
/>
|
/>
|
||||||
<MudTerminal
|
<MudTerminal
|
||||||
autofocus={activeTab === tab.id}
|
autofocus={activeTab === tab.id}
|
||||||
@@ -391,12 +545,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mud-mdi-pane {
|
.mud-mdi-pane {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex !important; /* Force display flex */
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||||
import { activeOutputHistory, addToOutputHistory, addToInputHistory, navigateInputHistory, activeInputHistoryIndex, activeConnection, uiSettings, accessibilitySettings, activeInputHistory } from '$lib/stores/mudStore';
|
import { activeOutputHistory, addToOutputHistory, addToInputHistory, navigateInputHistory, activeInputHistoryIndex, activeConnection, uiSettings, accessibilitySettings, activeInputHistory, activeProfileId, connectionStatus } from '$lib/stores/mudStore';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import AnsiToHtml from 'ansi-to-html';
|
import AnsiToHtml from 'ansi-to-html';
|
||||||
import { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
import { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
||||||
|
|
||||||
|
// Create safe defaults for reactivity
|
||||||
|
$: safeOutputHistory = $activeOutputHistory || [];
|
||||||
|
$: safeActiveProfileId = $activeProfileId || null;
|
||||||
|
|
||||||
// Create event dispatcher
|
// Create event dispatcher
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle input submission
|
// Handle input submission
|
||||||
function handleSubmit(event: Event) {
|
async function handleSubmit(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!currentInput.trim()) return;
|
if (!currentInput.trim()) return;
|
||||||
@@ -113,16 +117,34 @@
|
|||||||
addToOutputHistory(`> ********`, true);
|
addToOutputHistory(`> ********`, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the currently active profile id
|
||||||
|
const profileId = $activeProfileId;
|
||||||
|
|
||||||
// Send the command if connected
|
// Send the command if connected
|
||||||
if ($activeConnection) {
|
if (profileId) {
|
||||||
try {
|
// Get connection status for this profile
|
||||||
$activeConnection.send(currentInput);
|
const status = $connectionStatus[profileId];
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending command:', error);
|
if (status === 'connected') {
|
||||||
addToOutputHistory(`Error sending command: ${error}`, false, [{ pattern: 'Error', color: '#ff5555', isRegex: false }]);
|
try {
|
||||||
|
// Try using the activeConnection first
|
||||||
|
if ($activeConnection) {
|
||||||
|
$activeConnection.send(currentInput);
|
||||||
|
} else {
|
||||||
|
// If not available, use the ConnectionManager directly
|
||||||
|
const { ConnectionManager } = await import('$lib/connection/ConnectionManager');
|
||||||
|
const connectionManager = ConnectionManager.getInstance();
|
||||||
|
connectionManager.send(profileId, currentInput);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending command:', error);
|
||||||
|
addToOutputHistory(`Error sending command: ${error}`, false, [{ pattern: 'Error', color: '#ff5555', isRegex: false }]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addToOutputHistory(`Not connected to any MUD server. Status: ${status || 'disconnected'}`, false, [{ pattern: 'Not connected', color: '#ff5555', isRegex: false }]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addToOutputHistory('Not connected to any MUD server.', false, [{ pattern: 'Not connected', color: '#ff5555', isRegex: false }]);
|
addToOutputHistory('No profile selected.', false, [{ pattern: 'No profile', color: '#ff5555', isRegex: false }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the input
|
// Clear the input
|
||||||
@@ -266,16 +288,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch output history changes to scroll to bottom
|
// Watch output history and profile changes to update display
|
||||||
$: {
|
$: {
|
||||||
if ($activeOutputHistory) {
|
if (safeOutputHistory.length > 0) {
|
||||||
|
console.log('Active output history changed, updating terminal display');
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
// Update message elements when output history changes
|
// Update message elements when output history changes
|
||||||
setTimeout(updateMessageElements, 0);
|
setTimeout(updateMessageElements, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
// Every time the active profile changes, update the terminal content
|
||||||
|
if ($activeProfileId) {
|
||||||
|
console.log(`Active profile is now: ${$activeProfileId}, updating output display`);
|
||||||
|
updateOutputDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to update the displayed output based on the active profile
|
||||||
|
async function updateOutputDisplay() {
|
||||||
|
console.log('Updating output display...');
|
||||||
|
await tick(); // Wait for Svelte to update
|
||||||
|
scrollToBottom();
|
||||||
|
updateMessageElements();
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
console.log('MudTerminal component mounted for profile:', $activeProfileId);
|
||||||
|
|
||||||
// Initialize accessibility manager
|
// Initialize accessibility manager
|
||||||
accessibilityManager = new AccessibilityManager();
|
accessibilityManager = new AccessibilityManager();
|
||||||
|
|
||||||
@@ -318,7 +359,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:keydown={handleOutputKeyDown}
|
on:keydown={handleOutputKeyDown}
|
||||||
style="font-family: {$uiSettings.font}; font-size: {$accessibilitySettings.fontSize}px; line-height: {$accessibilitySettings.lineSpacing};">
|
style="font-family: {$uiSettings.font}; font-size: {$accessibilitySettings.fontSize}px; line-height: {$accessibilitySettings.lineSpacing};">
|
||||||
{#each $activeOutputHistory as item (item.id)}
|
{#each safeOutputHistory as item (item.id)}
|
||||||
<!-- For input lines, keep them as a single block -->
|
<!-- For input lines, keep them as a single block -->
|
||||||
{#if item.isInput}
|
{#if item.isInput}
|
||||||
<div class="mud-terminal-line mud-input-line" tabindex="-1">
|
<div class="mud-terminal-line mud-input-line" tabindex="-1">
|
||||||
|
|||||||
191
src/lib/connection/ConnectionManager.ts
Normal file
191
src/lib/connection/ConnectionManager.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { writable, get } from 'svelte/store';
|
||||||
|
import { MudConnection } from './MudConnection';
|
||||||
|
import type { GmcpHandler } from '$lib/gmcp/GmcpHandler';
|
||||||
|
import { connectionStatus } from '$lib/stores/mudStore';
|
||||||
|
|
||||||
|
// Store for active MUD connections (persistent across tab switches)
|
||||||
|
export const activeConnections = writable<{ [key: string]: MudConnection }>({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionManager - Singleton service to manage MUD connections
|
||||||
|
* This ensures connections stay alive even when components are unmounted during tab switches
|
||||||
|
*/
|
||||||
|
export class ConnectionManager {
|
||||||
|
private static instance: ConnectionManager;
|
||||||
|
|
||||||
|
// Private constructor for singleton pattern
|
||||||
|
private constructor() {
|
||||||
|
console.log('ConnectionManager initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance of ConnectionManager
|
||||||
|
*/
|
||||||
|
public static getInstance(): ConnectionManager {
|
||||||
|
if (!ConnectionManager.instance) {
|
||||||
|
ConnectionManager.instance = new ConnectionManager();
|
||||||
|
}
|
||||||
|
return ConnectionManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an existing connection without creating a new one
|
||||||
|
*/
|
||||||
|
public getExistingConnection(profileId: string): MudConnection | null {
|
||||||
|
const connections = get(activeConnections);
|
||||||
|
return connections[profileId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new connection or return an existing one
|
||||||
|
*/
|
||||||
|
public getConnection(profileId: string, options: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
useSSL?: boolean;
|
||||||
|
gmcpHandler?: GmcpHandler;
|
||||||
|
}): MudConnection {
|
||||||
|
// Get current connections
|
||||||
|
const connections = get(activeConnections);
|
||||||
|
|
||||||
|
// Check if a connection already exists for this profile
|
||||||
|
if (connections[profileId]) {
|
||||||
|
console.log(`Returning existing connection for profile ${profileId}`);
|
||||||
|
return connections[profileId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new connection
|
||||||
|
console.log(`Creating new connection for profile ${profileId}`);
|
||||||
|
const connection = new MudConnection({
|
||||||
|
host: options.host,
|
||||||
|
port: options.port,
|
||||||
|
useSSL: options.useSSL,
|
||||||
|
gmcpHandler: options.gmcpHandler
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up event handlers
|
||||||
|
this.setupConnectionEvents(connection, profileId);
|
||||||
|
|
||||||
|
// Store the connection
|
||||||
|
activeConnections.update(connections => ({
|
||||||
|
...connections,
|
||||||
|
[profileId]: connection
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Return the connection
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to a MUD server
|
||||||
|
*/
|
||||||
|
public connect(profileId: string, options: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
useSSL?: boolean;
|
||||||
|
gmcpHandler?: GmcpHandler;
|
||||||
|
}): void {
|
||||||
|
const connection = this.getConnection(profileId, options);
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => ({
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'connecting'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
connection.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from a MUD server
|
||||||
|
*/
|
||||||
|
public disconnect(profileId: string): void {
|
||||||
|
const connections = get(activeConnections);
|
||||||
|
|
||||||
|
if (connections[profileId]) {
|
||||||
|
connections[profileId].disconnect();
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => ({
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'disconnected'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send text to a MUD server
|
||||||
|
*/
|
||||||
|
public send(profileId: string, text: string): void {
|
||||||
|
const connections = get(activeConnections);
|
||||||
|
|
||||||
|
if (connections[profileId]) {
|
||||||
|
connections[profileId].send(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a connection and remove it
|
||||||
|
*/
|
||||||
|
public closeConnection(profileId: string): void {
|
||||||
|
const connections = get(activeConnections);
|
||||||
|
|
||||||
|
if (connections[profileId]) {
|
||||||
|
// Disconnect first
|
||||||
|
connections[profileId].disconnect();
|
||||||
|
|
||||||
|
// Then remove from store
|
||||||
|
activeConnections.update(connections => {
|
||||||
|
const newConnections = { ...connections };
|
||||||
|
delete newConnections[profileId];
|
||||||
|
return newConnections;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => {
|
||||||
|
const newStatuses = { ...statuses };
|
||||||
|
delete newStatuses[profileId];
|
||||||
|
return newStatuses;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up event handlers for a connection
|
||||||
|
*/
|
||||||
|
private setupConnectionEvents(connection: MudConnection, profileId: string): void {
|
||||||
|
// Handle connection established
|
||||||
|
connection.on('connected', () => {
|
||||||
|
console.log(`ConnectionManager: Connection established for profile ${profileId}`);
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => ({
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'connected'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle connection closed
|
||||||
|
connection.on('disconnected', () => {
|
||||||
|
console.log(`ConnectionManager: Connection closed for profile ${profileId}`);
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => ({
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'disconnected'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle connection error
|
||||||
|
connection.on('error', (error) => {
|
||||||
|
console.error(`ConnectionManager: Connection error for profile ${profileId}:`, error);
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
connectionStatus.update(statuses => ({
|
||||||
|
...statuses,
|
||||||
|
[profileId]: 'error'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,10 +49,30 @@ export const activeProfile = derived(
|
|||||||
|
|
||||||
// Derived store for active connection
|
// Derived store for active connection
|
||||||
export const activeConnection = derived(
|
export const activeConnection = derived(
|
||||||
[connections, activeProfileId],
|
[connections, activeProfileId, connectionStatus],
|
||||||
([$connections, $activeProfileId]) => {
|
([$connections, $activeProfileId, $connectionStatus]) => {
|
||||||
|
// First check if there's an active profile ID
|
||||||
if (!$activeProfileId) return null;
|
if (!$activeProfileId) return null;
|
||||||
return $connections[$activeProfileId] || null;
|
|
||||||
|
// Check for the connection in the local connections store
|
||||||
|
const localConnection = $connections[$activeProfileId];
|
||||||
|
if (localConnection) return localConnection;
|
||||||
|
|
||||||
|
// If not in local store but the connection status shows connected/connecting,
|
||||||
|
// use the ConnectionManager's connection instead
|
||||||
|
if ($connectionStatus[$activeProfileId] === 'connected' || $connectionStatus[$activeProfileId] === 'connecting') {
|
||||||
|
try {
|
||||||
|
// We can't do a dynamic import in a derived store, so we'll return null
|
||||||
|
// and let the components handle getting the connection from the manager directly
|
||||||
|
console.log('Connection not in local store but status indicates it exists in manager');
|
||||||
|
return null;
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Error getting connection from manager:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,14 @@ export class EventEmitter {
|
|||||||
return this.on(event, onceWrapper);
|
return this.on(event, onceWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
off(event: string, listener: Function): this {
|
off(event: string, listener?: Function): this {
|
||||||
if (this.events[event]) {
|
if (this.events[event]) {
|
||||||
this.events[event] = this.events[event].filter(l => l !== listener);
|
if (listener) {
|
||||||
|
this.events[event] = this.events[event].filter(l => l !== listener);
|
||||||
|
} else {
|
||||||
|
// Remove all listeners for this event if no specific listener is provided
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
triggers,
|
triggers,
|
||||||
uiSettings,
|
uiSettings,
|
||||||
accessibilitySettings,
|
accessibilitySettings,
|
||||||
addToOutputHistory
|
addToOutputHistory,
|
||||||
|
connectionStatus,
|
||||||
|
connections,
|
||||||
|
outputHistory
|
||||||
} from '$lib/stores/mudStore';
|
} from '$lib/stores/mudStore';
|
||||||
import { ProfileManager } from '$lib/profiles/ProfileManager';
|
import { ProfileManager } from '$lib/profiles/ProfileManager';
|
||||||
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
||||||
@@ -78,6 +81,14 @@
|
|||||||
triggerSystem = new TriggerSystem();
|
triggerSystem = new TriggerSystem();
|
||||||
console.log('Trigger system initialized');
|
console.log('Trigger system initialized');
|
||||||
|
|
||||||
|
// Initialize the connection status store
|
||||||
|
connectionStatus.set({});
|
||||||
|
console.log('Connection status store initialized');
|
||||||
|
|
||||||
|
// Clear connections store to start fresh
|
||||||
|
connections.set({});
|
||||||
|
console.log('Connections store cleared');
|
||||||
|
|
||||||
// Load profiles first to ensure we have them
|
// Load profiles first to ensure we have them
|
||||||
console.log('Loading profiles...');
|
console.log('Loading profiles...');
|
||||||
loadProfiles();
|
loadProfiles();
|
||||||
@@ -114,6 +125,15 @@
|
|||||||
console.log('No active profile selected, setting first profile as active');
|
console.log('No active profile selected, setting first profile as active');
|
||||||
activeProfileId.set($profiles[0].id);
|
activeProfileId.set($profiles[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize output history for all profiles
|
||||||
|
const outputHistoryObject = {};
|
||||||
|
$profiles.forEach(profile => {
|
||||||
|
outputHistoryObject[profile.id] = [];
|
||||||
|
});
|
||||||
|
outputHistory.set(outputHistoryObject);
|
||||||
|
console.log('Output history initialized for all profiles');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during page initialization:', error);
|
console.error('Error during page initialization:', error);
|
||||||
console.error('Error details:', error.message, error.stack);
|
console.error('Error details:', error.message, error.stack);
|
||||||
|
|||||||
Reference in New Issue
Block a user