Fix TTS module initialization and dependency issues. Update module IDs for consistency, improve circular dependency detection, and fix UI Controller event handling.
This commit is contained in:
+165
-162
@@ -4,10 +4,11 @@ import { ModuleEvent } from './base-module.js';
|
||||
|
||||
class UIController extends BaseModule {
|
||||
constructor() {
|
||||
super('ui-controller');
|
||||
super('ui-controller', 'UI Controller');
|
||||
|
||||
// Declare dependencies on TTS, animation-queue, and our new UI modules
|
||||
this.dependencies = ['tts', 'animation-queue', 'ui-display-handler', 'ui-input-handler', 'ui-effects'];
|
||||
// Remove 'tts' from direct dependencies to break circular dependency
|
||||
// UI Controller will access TTS through the Game Loop instead
|
||||
this.dependencies = ['animation-queue', 'ui-display-handler', 'ui-input-handler', 'ui-effects', 'text-buffer', 'socket-client'];
|
||||
|
||||
// References to sub-modules
|
||||
this.displayHandler = null;
|
||||
@@ -32,55 +33,74 @@ class UIController extends BaseModule {
|
||||
|
||||
// Add TTS toggle state
|
||||
this.ttsEnabled = false;
|
||||
this.ttsAvailable = true; // Add TTS availability state
|
||||
|
||||
// Bind methods that use 'this' internally or are used as callbacks/event handlers
|
||||
this.initialize = this.initialize.bind(this); // Bind initialize as it calls dispatchEvent
|
||||
this.handleCommand = this.handleCommand.bind(this); // Bind event handler
|
||||
this.displayText = this.displayText.bind(this); // Bind if passed as callback
|
||||
this.setupBookInterface = this.setupBookInterface.bind(this);
|
||||
this.applyBookSizing = this.applyBookSizing.bind(this);
|
||||
this.setupEventListeners = this.setupEventListeners.bind(this);
|
||||
this.setupMainUI = this.setupMainUI.bind(this);
|
||||
this.initializeTextBuffer = this.initializeTextBuffer.bind(this);
|
||||
this.showUI = this.showUI.bind(this);
|
||||
this.hideUI = this.hideUI.bind(this);
|
||||
this.clearDisplay = this.clearDisplay.bind(this);
|
||||
this.sendCommand = this.sendCommand.bind(this);
|
||||
this.updateButtonStates = this.updateButtonStates.bind(this);
|
||||
|
||||
// Store a bound version of dispatchEvent for use in methods
|
||||
this._dispatchModuleEvent = (name, detail) => {
|
||||
document.dispatchEvent(new CustomEvent(name, {
|
||||
detail: { moduleId: this.id, ...detail },
|
||||
bubbles: true
|
||||
}));
|
||||
};
|
||||
// Bind methods using the parent class bindMethods utility
|
||||
this.bindMethods([
|
||||
'initialize',
|
||||
'handleCommand',
|
||||
'displayText',
|
||||
'setupBookInterface',
|
||||
'applyBookSizing',
|
||||
'setupEventListeners',
|
||||
'setupMainUI',
|
||||
'initializeTextBuffer',
|
||||
'showUI',
|
||||
'hideUI',
|
||||
'clearDisplay',
|
||||
'sendCommand',
|
||||
'updateButtonStates'
|
||||
]);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.reportProgress(0, 'Initializing UI Controller');
|
||||
|
||||
try {
|
||||
this.reportProgress(0, 'Initializing UI Controller');
|
||||
|
||||
this.reportProgress(20, 'Setting up book interface');
|
||||
|
||||
// Set up book interface
|
||||
this.setupBookInterface();
|
||||
|
||||
this.reportProgress(30, 'Setting up UI components');
|
||||
this.reportProgress(30, 'Getting module dependencies');
|
||||
|
||||
// Get module references
|
||||
this.displayHandler = moduleRegistry.getModule('ui-display-handler');
|
||||
this.inputHandler = moduleRegistry.getModule('ui-input-handler');
|
||||
this.effects = moduleRegistry.getModule('ui-effects');
|
||||
// Get module references using parent's getModule method
|
||||
this.displayHandler = this.getModule('ui-display-handler');
|
||||
this.inputHandler = this.getModule('ui-input-handler');
|
||||
this.effects = this.getModule('ui-effects');
|
||||
this.textBuffer = this.getModule('text-buffer');
|
||||
this.socketClient = this.getModule('socket-client');
|
||||
this.animationQueue = this.getModule('animation-queue');
|
||||
|
||||
// Get additional dependencies
|
||||
this.textBuffer = moduleRegistry.getModule('text-buffer');
|
||||
this.ttsHandler = moduleRegistry.getModule('tts');
|
||||
this.socketClient = moduleRegistry.getModule('socket-client');
|
||||
this.animationQueue = moduleRegistry.getModule('animation-queue');
|
||||
// Check for required UI modules
|
||||
if (!this.displayHandler) {
|
||||
console.error('UI Controller: Display handler module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.displayHandler || !this.inputHandler || !this.effects) {
|
||||
console.error('UI Controller: Required UI modules not found');
|
||||
if (!this.inputHandler) {
|
||||
console.error('UI Controller: Input handler module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.effects) {
|
||||
console.error('UI Controller: UI effects module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for other required modules
|
||||
if (!this.textBuffer) {
|
||||
console.error('UI Controller: Text buffer module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.socketClient) {
|
||||
console.error('UI Controller: Socket client module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.animationQueue) {
|
||||
console.error('UI Controller: Animation queue module not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,24 +109,25 @@ class UIController extends BaseModule {
|
||||
// Set up event listeners between components
|
||||
this.setupEventListeners();
|
||||
|
||||
this.reportProgress(80, 'Finalizing UI initialization');
|
||||
this.reportProgress(70, 'Setting up main UI');
|
||||
|
||||
// Initialize main UI container
|
||||
await this.setupMainUI();
|
||||
|
||||
this.reportProgress(80, 'Initializing text buffer');
|
||||
|
||||
// Initialize text buffer handler
|
||||
this.initializeTextBuffer();
|
||||
|
||||
this.reportProgress(100, 'UI Controller ready');
|
||||
|
||||
this.isReady = true;
|
||||
this.isVisible = true;
|
||||
this.reportProgress(100, 'UI Controller ready');
|
||||
this.dispatchEvent(new ModuleEvent('ui:ready', { controller: this }));
|
||||
|
||||
// Start ambient effects
|
||||
this.effects.startAmbientEffects();
|
||||
|
||||
// Use the DOM event API directly instead of this.dispatchEvent
|
||||
this._dispatchModuleEvent('ui:ready', { controller: this });
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error initializing UI Controller:', error);
|
||||
@@ -151,95 +172,41 @@ class UIController extends BaseModule {
|
||||
});
|
||||
|
||||
// Listen for text display events - use arrow function to preserve context
|
||||
document.addEventListener('ui:text:complete', () => {
|
||||
// Use the DOM event API directly
|
||||
this._dispatchModuleEvent('ui:ready:for:next', {});
|
||||
document.addEventListener('ui:text:complete', (event) => {
|
||||
console.log('UIController: Text complete event received, ready for next text');
|
||||
});
|
||||
|
||||
// Listen for socket connection events
|
||||
document.addEventListener('socket:connected', () => {
|
||||
console.log('UI Controller: Socket connected');
|
||||
console.log('UIController: Socket connected');
|
||||
this.updateButtonStates();
|
||||
});
|
||||
|
||||
document.addEventListener('socket:disconnected', () => {
|
||||
console.log('UI Controller: Socket disconnected');
|
||||
console.log('UIController: Socket disconnected');
|
||||
this.updateButtonStates();
|
||||
});
|
||||
|
||||
// Handle speed reset
|
||||
const speedReset = document.getElementById('speed_reset');
|
||||
if (speedReset) {
|
||||
speedReset.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const speedSlider = document.getElementById('speed');
|
||||
if (speedSlider) {
|
||||
speedSlider.value = 50;
|
||||
if (this.animationQueue) {
|
||||
this.animationQueue.setSpeed(1.0);
|
||||
}
|
||||
// Listen for TTS state change events
|
||||
document.addEventListener('tts:stateChange', (event) => {
|
||||
if (event.detail) {
|
||||
if (typeof event.detail.enabled === 'boolean') {
|
||||
this.ttsEnabled = event.detail.enabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle speed slider change for animation speed
|
||||
const speedSlider = document.getElementById('speed');
|
||||
if (speedSlider) {
|
||||
speedSlider.addEventListener('input', (e) => {
|
||||
if (this.animationQueue) {
|
||||
// Convert slider value (0-100) to animation speed
|
||||
// Using formula from Documentation.md: lower values = slower speed
|
||||
const value = parseInt(e.target.value);
|
||||
const speed = Math.pow(100.0 - value, 3) / 10000 * 10 + 0.01;
|
||||
this.animationQueue.setSpeed(speed);
|
||||
console.log(`UI Controller: Animation speed set to ${speed.toFixed(3)}`);
|
||||
|
||||
// Save to persistence manager if available
|
||||
if (window.PersistenceManager) {
|
||||
window.PersistenceManager.updatePreference('animation', 'speed', value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial speed from persistence manager if available
|
||||
if (window.PersistenceManager) {
|
||||
const savedSpeed = window.PersistenceManager.getPreference('animation', 'speed', 50);
|
||||
speedSlider.value = savedSpeed;
|
||||
// Apply initial speed
|
||||
if (this.animationQueue) {
|
||||
const speed = Math.pow(100.0 - savedSpeed, 3) / 10000 * 10 + 0.01;
|
||||
this.animationQueue.setSpeed(speed);
|
||||
if (typeof event.detail.available === 'boolean') {
|
||||
this.ttsAvailable = event.detail.available;
|
||||
}
|
||||
this.updateButtonStates();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle speech toggle with proper state management
|
||||
const speechToggle = document.getElementById('speech');
|
||||
if (speechToggle && this.ttsHandler) {
|
||||
// Remove disabled attribute to make it clickable
|
||||
speechToggle.removeAttribute('disabled');
|
||||
|
||||
speechToggle.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
console.log('Speech toggle clicked');
|
||||
|
||||
// Toggle TTS state
|
||||
if (this.ttsHandler && typeof this.ttsHandler.toggle === 'function') {
|
||||
this.ttsEnabled = this.ttsHandler.toggle();
|
||||
|
||||
// Update button text
|
||||
speechToggle.textContent = this.ttsEnabled ? 'mute' : 'speech';
|
||||
|
||||
// Save preference if persistence manager is available
|
||||
const persistenceManager = moduleRegistry.getModule('persistence-manager');
|
||||
if (persistenceManager) {
|
||||
persistenceManager.updatePreference('tts', 'enabled', this.ttsEnabled);
|
||||
}
|
||||
|
||||
console.log(`UI Controller: TTS ${this.ttsEnabled ? 'enabled' : 'disabled'}`);
|
||||
} else {
|
||||
console.warn('TTS Handler does not have toggle method');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Listen for TTS availability events
|
||||
document.addEventListener('tts:availability', (event) => {
|
||||
if (event.detail && typeof event.detail.available === 'boolean') {
|
||||
this.ttsAvailable = event.detail.available;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
});
|
||||
|
||||
// Add options button to controls section
|
||||
const controlsSection = document.getElementById('controls');
|
||||
@@ -251,53 +218,35 @@ class UIController extends BaseModule {
|
||||
optionsButton.href = '#';
|
||||
optionsButton.textContent = 'options';
|
||||
optionsButton.title = 'Show game options';
|
||||
|
||||
// Add event listener
|
||||
optionsButton.className = 'control-button';
|
||||
optionsButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const optionsUI = moduleRegistry.getModule('options-ui');
|
||||
if (optionsUI && optionsUI.toggle) {
|
||||
optionsUI.toggle();
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent('ui:showOptions'));
|
||||
});
|
||||
|
||||
// Add to controls
|
||||
controlsSection.appendChild(document.createTextNode(' | '));
|
||||
controlsSection.appendChild(optionsButton);
|
||||
}
|
||||
|
||||
// Add speech toggle button
|
||||
const speechToggle = document.getElementById('speech-toggle');
|
||||
if (speechToggle) {
|
||||
speechToggle.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
// Dispatch an event for the TTS module to handle instead of calling directly
|
||||
document.dispatchEvent(new CustomEvent('tts:toggle'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Enable all controls buttons
|
||||
const controlButtons = document.querySelectorAll('#controls a');
|
||||
controlButtons.forEach(button => {
|
||||
button.removeAttribute('disabled');
|
||||
// Listen for window resize events
|
||||
window.addEventListener('resize', () => {
|
||||
this.applyBookSizing();
|
||||
});
|
||||
|
||||
// Book click for fast-forwarding - make sure it triggers the animation queue
|
||||
if (this.bookElement) {
|
||||
this.bookElement.addEventListener('click', (event) => {
|
||||
// Only if not clicking on a link or control
|
||||
if (event.target.tagName !== 'A' &&
|
||||
!event.target.closest('#controls') &&
|
||||
!event.target.closest('#command_input')) {
|
||||
if (this.animationQueue) {
|
||||
console.log('UI Controller: Fast-forwarding animations');
|
||||
this.animationQueue.fastForward();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Space key for fast-forwarding
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === ' ' &&
|
||||
document.activeElement.tagName !== 'TEXTAREA' &&
|
||||
document.activeElement.tagName !== 'INPUT') {
|
||||
if (this.animationQueue) {
|
||||
console.log('UI Controller: Fast-forwarding animations (space key)');
|
||||
this.animationQueue.fastForward();
|
||||
e.preventDefault(); // Prevent page scrolling
|
||||
}
|
||||
// Listen for key events
|
||||
document.addEventListener('keydown', (event) => {
|
||||
// Pass to input handler
|
||||
if (this.inputHandler) {
|
||||
this.inputHandler.handleKeyboardInput(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -319,10 +268,30 @@ class UIController extends BaseModule {
|
||||
initializeTextBuffer() {
|
||||
// Initialize text buffer handling
|
||||
if (this.textBuffer) {
|
||||
console.log('UIController: Setting up text buffer callback');
|
||||
this.textBuffer.setOnSentenceReady((text, callback) => {
|
||||
console.log('UI Controller: Displaying sentence');
|
||||
this.displayText(text).then(callback);
|
||||
console.log('UIController: Received sentence from text buffer, displaying');
|
||||
|
||||
// Use the display handler to show text with proper formatting and TTS
|
||||
this.displayText(text)
|
||||
.then(() => {
|
||||
console.log('UIController: Display of sentence completed, continuing...');
|
||||
|
||||
// Signal that we're ready to process the next sentence
|
||||
if (typeof callback === 'function') {
|
||||
// Use a small timeout to prevent potential stack overflow with many sentences
|
||||
setTimeout(() => callback(), 10);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('UIController: Error displaying text:', error);
|
||||
// Continue anyway to prevent blocking
|
||||
if (typeof callback === 'function') callback();
|
||||
});
|
||||
});
|
||||
console.log('UIController: Text buffer callback set up');
|
||||
} else {
|
||||
console.warn('UIController: Text buffer module not found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +311,20 @@ class UIController extends BaseModule {
|
||||
break;
|
||||
case 'input':
|
||||
if (this.socketClient) {
|
||||
this.socketClient.sendCommand(command.text);
|
||||
console.log(`UI Controller: Sending command to socket: "${command.text}"`);
|
||||
const success = this.socketClient.sendCommand(command.text);
|
||||
|
||||
if (success) {
|
||||
console.log('UI Controller: Command sent successfully');
|
||||
} else {
|
||||
console.error('UI Controller: Failed to send command to socket');
|
||||
// Display an error message to the user
|
||||
this.displayHandler.displayText('⚠️ Unable to send command. Server connection might be lost.', {
|
||||
style: { color: '#990000' }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error('UI Controller: Socket client not available for sending commands');
|
||||
}
|
||||
break;
|
||||
case 'menu':
|
||||
@@ -354,7 +336,7 @@ class UIController extends BaseModule {
|
||||
break;
|
||||
default:
|
||||
// Handle general UI commands or pass to game logic
|
||||
this._dispatchModuleEvent('ui:command', command);
|
||||
this.dispatchEvent(new ModuleEvent('ui:command', command));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +351,7 @@ class UIController extends BaseModule {
|
||||
const saveButton = document.getElementById('save');
|
||||
const loadButton = document.getElementById('reload');
|
||||
const restartButton = document.getElementById('rewind');
|
||||
const speechToggle = document.getElementById('speech-toggle');
|
||||
|
||||
// Update save button state
|
||||
if (saveButton) {
|
||||
@@ -396,6 +379,26 @@ class UIController extends BaseModule {
|
||||
restartButton.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Update speech toggle button state
|
||||
if (speechToggle) {
|
||||
// Update the button appearance based on TTS state
|
||||
if (this.ttsEnabled) {
|
||||
speechToggle.classList.add('active');
|
||||
speechToggle.title = 'Disable speech';
|
||||
} else {
|
||||
speechToggle.classList.remove('active');
|
||||
speechToggle.title = 'Enable speech';
|
||||
}
|
||||
|
||||
// Disable the button completely if TTS is not available
|
||||
if (this.ttsAvailable === false) {
|
||||
speechToggle.setAttribute('disabled', 'disabled');
|
||||
speechToggle.title = 'Speech not available';
|
||||
} else {
|
||||
speechToggle.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
|
||||
Reference in New Issue
Block a user