Refactored modules and updated loader.
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* Animation Queue Module
|
||||
* Handles scheduling and executing animations with proper resource management
|
||||
* and synchronization with TTS
|
||||
*/
|
||||
import { BaseModule } from './base-module.js';
|
||||
|
||||
class AnimationQueueModule extends BaseModule {
|
||||
constructor() {
|
||||
super('animation-queue', 'Animation Queue');
|
||||
|
||||
// Module dependencies
|
||||
this.dependencies = ['tts-player'];
|
||||
|
||||
// Queue of scheduled animations/functions
|
||||
this.timeoutQueue = [];
|
||||
|
||||
// Animation timing properties - use parent's config system
|
||||
this.updateConfig({
|
||||
speed: 0.05, // Base animation speed (seconds per character)
|
||||
fastForwardEnabled: false
|
||||
});
|
||||
|
||||
this.delay = 0; // Current accumulated delay
|
||||
this.tts = null; // TTS module reference
|
||||
|
||||
// Bind methods using parent's bindMethods utility
|
||||
this.bindMethods([
|
||||
'schedule',
|
||||
'fastForward',
|
||||
'clearAll',
|
||||
'setSpeed',
|
||||
'beginFastForward',
|
||||
'endFastForward',
|
||||
'emitAnimationComplete',
|
||||
'cleanupStaleTasks',
|
||||
'isAnyTtsSpeaking'
|
||||
]);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
try {
|
||||
this.reportProgress(20, "Initializing Animation Queue");
|
||||
|
||||
// Try to get the TTS module, but it's not a hard dependency
|
||||
// We'll check for it again at runtime when needed
|
||||
this.tts = this.getModule('tts-player');
|
||||
if (!this.tts) {
|
||||
console.log("Animation Queue: TTS Player module not found yet, will try again when needed");
|
||||
}
|
||||
|
||||
this.reportProgress(100, "Animation Queue ready");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error initializing Animation Queue:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a function to execute after a delay, with optional TTS synchronization
|
||||
* @param {Function} func - Function to execute
|
||||
* @param {number} delay - Delay in milliseconds
|
||||
* @param {Object} options - Optional parameters including TTS text
|
||||
* @returns {number} - Timeout ID for cancellation
|
||||
*/
|
||||
schedule(func, delay, options = {}) {
|
||||
if (typeof func !== 'function') {
|
||||
console.error('AnimationQueue: Invalid function provided to schedule');
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Adjust delay based on fast-forward or speed settings
|
||||
const actualDelay = this.config.fastForwardEnabled ? 0 : Math.max(0, delay * this.config.speed);
|
||||
|
||||
// Record the delay for tracking
|
||||
this.delay = Math.max(this.delay, delay);
|
||||
|
||||
// If we don't have a reference to the TTS module yet, try to get it
|
||||
if (!this.tts) {
|
||||
this.tts = this.getModule('tts-player');
|
||||
}
|
||||
|
||||
// Handle TTS if text is provided and TTS is available and enabled
|
||||
let ttsSpeaking = false;
|
||||
if (options.text && this.tts && typeof this.tts.isEnabled === 'function' && this.tts.isEnabled()) {
|
||||
// If we're fast forwarding, don't speak
|
||||
if (!this.config.fastForwardEnabled) {
|
||||
ttsSpeaking = true;
|
||||
// Request TTS to speak the text
|
||||
this.tts.speak(options.text, (result) => {
|
||||
ttsSpeaking = false;
|
||||
|
||||
// Check if this was keeping the queue busy
|
||||
if (this.timeoutQueue.length === 0) {
|
||||
this.emitAnimationComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create a timeout object
|
||||
const timeoutObject = {
|
||||
func: func,
|
||||
delay: actualDelay,
|
||||
timeoutId: null,
|
||||
executed: false,
|
||||
startTime: Date.now(),
|
||||
ttsSpeaking: ttsSpeaking,
|
||||
|
||||
// Add an execute method that marks the timeout as executed
|
||||
execute: function() {
|
||||
if (!this.executed) {
|
||||
this.func();
|
||||
this.executed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add to queue
|
||||
this.timeoutQueue.push(timeoutObject);
|
||||
|
||||
// If we're fast forwarding, execute immediately
|
||||
if (this.config.fastForwardEnabled) {
|
||||
timeoutObject.execute();
|
||||
return -1; // No timeout ID since it executed immediately
|
||||
}
|
||||
|
||||
// Schedule the timeout
|
||||
timeoutObject.timeoutId = setTimeout(() => {
|
||||
// Execute the function
|
||||
timeoutObject.execute();
|
||||
|
||||
// Remove from queue
|
||||
const index = this.timeoutQueue.indexOf(timeoutObject);
|
||||
if (index !== -1) {
|
||||
this.timeoutQueue.splice(index, 1);
|
||||
}
|
||||
|
||||
// If queue is empty and no TTS is speaking, emit animation complete
|
||||
if (this.timeoutQueue.length === 0 && !this.isAnyTtsSpeaking()) {
|
||||
this.emitAnimationComplete();
|
||||
}
|
||||
|
||||
}, actualDelay);
|
||||
|
||||
return timeoutObject.timeoutId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an animation complete event
|
||||
*/
|
||||
emitAnimationComplete() {
|
||||
// Only emit if queue is empty and no TTS is speaking
|
||||
if (this.timeoutQueue.length === 0 && !this.isAnyTtsSpeaking()) {
|
||||
// Use parent's dispatchEvent method
|
||||
this.dispatchEvent('ui:animation:complete', {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any animation tasks that might have been missed
|
||||
* (e.g. due to browser tab being inactive)
|
||||
*/
|
||||
cleanupStaleTasks() {
|
||||
const now = Date.now();
|
||||
const staleTasks = [];
|
||||
|
||||
// Find stale tasks
|
||||
this.timeoutQueue.forEach(task => {
|
||||
// If task has been running for more than 10 seconds, consider it stale
|
||||
if (now - task.startTime > 10000 && !task.executed) {
|
||||
staleTasks.push(task);
|
||||
}
|
||||
});
|
||||
|
||||
// Execute and remove stale tasks
|
||||
staleTasks.forEach(task => {
|
||||
console.log('AnimationQueue: Cleaning up stale task');
|
||||
|
||||
// Clear the timeout
|
||||
if (task.timeoutId !== null) {
|
||||
clearTimeout(task.timeoutId);
|
||||
}
|
||||
|
||||
// Execute the task
|
||||
task.execute();
|
||||
|
||||
// Remove from queue
|
||||
const index = this.timeoutQueue.indexOf(task);
|
||||
if (index !== -1) {
|
||||
this.timeoutQueue.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any TTS is currently speaking
|
||||
* @returns {boolean} - True if TTS is speaking
|
||||
*/
|
||||
isAnyTtsSpeaking() {
|
||||
// If we don't have a reference to the TTS module yet, try to get it
|
||||
if (!this.tts) {
|
||||
this.tts = this.getModule('tts-player');
|
||||
}
|
||||
|
||||
// Check if TTS is speaking
|
||||
if (this.tts && typeof this.tts.isSpeaking === 'function') {
|
||||
return this.tts.isSpeaking();
|
||||
}
|
||||
|
||||
// Default to false if TTS module is not available
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast forward all pending animations and stop TTS
|
||||
*/
|
||||
fastForward() {
|
||||
if (this.timeoutQueue.length === 0) {
|
||||
console.log('AnimationQueue: No animations to fast forward');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`AnimationQueue: Fast forwarding ${this.timeoutQueue.length} pending items`);
|
||||
|
||||
// If we don't have a reference to the TTS module yet, try to get it
|
||||
if (!this.tts) {
|
||||
this.tts = this.getModule('tts-player');
|
||||
}
|
||||
|
||||
// Stop any active TTS
|
||||
if (this.tts && typeof this.tts.stop === 'function') {
|
||||
this.tts.stop();
|
||||
}
|
||||
|
||||
// Execute all pending animations immediately
|
||||
this.timeoutQueue.forEach(timeout => {
|
||||
// Clear the timeout
|
||||
if (timeout.timeoutId !== null) {
|
||||
clearTimeout(timeout.timeoutId);
|
||||
timeout.timeoutId = null;
|
||||
}
|
||||
|
||||
// Clear TTS flag
|
||||
timeout.ttsSpeaking = false;
|
||||
|
||||
// Execute the function immediately
|
||||
timeout.execute();
|
||||
});
|
||||
|
||||
// Clear the queue
|
||||
this.timeoutQueue = [];
|
||||
|
||||
// Reset delay
|
||||
this.delay = 0;
|
||||
|
||||
// Update config using parent's updateConfig method
|
||||
this.updateConfig({ fastForwardEnabled: false });
|
||||
|
||||
// Emit animation complete event
|
||||
this.emitAnimationComplete();
|
||||
|
||||
// Log the fastforward completion
|
||||
console.log('AnimationQueue: Fast forward complete');
|
||||
|
||||
// Use parent's dispatchEvent method
|
||||
this.dispatchEvent('ui:animation:fastforward', { state: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin fast forwarding mode
|
||||
*/
|
||||
beginFastForward() {
|
||||
if (this.config.fastForwardEnabled) return;
|
||||
|
||||
// Update config using parent's updateConfig method
|
||||
this.updateConfig({ fastForwardEnabled: true });
|
||||
|
||||
// If we don't have a reference to the TTS module yet, try to get it
|
||||
if (!this.tts) {
|
||||
this.tts = this.getModule('tts-player');
|
||||
}
|
||||
|
||||
// Stop any active TTS
|
||||
if (this.tts && typeof this.tts.stop === 'function') {
|
||||
this.tts.stop();
|
||||
}
|
||||
|
||||
// Use parent's dispatchEvent method
|
||||
this.dispatchEvent('ui:animation:fastforward', { state: true });
|
||||
|
||||
console.log('AnimationQueue: Fast forward mode activated');
|
||||
}
|
||||
|
||||
/**
|
||||
* End fast forwarding mode
|
||||
*/
|
||||
endFastForward() {
|
||||
if (!this.config.fastForwardEnabled) return;
|
||||
|
||||
// Update config using parent's updateConfig method
|
||||
this.updateConfig({ fastForwardEnabled: false });
|
||||
|
||||
// Use parent's dispatchEvent method
|
||||
this.dispatchEvent('ui:animation:fastforward', { state: false });
|
||||
|
||||
console.log('AnimationQueue: Fast forward mode deactivated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all scheduled animations without executing them
|
||||
*/
|
||||
clearAll() {
|
||||
console.log(`Animation Queue: Clearing ${this.timeoutQueue.length} pending items`);
|
||||
|
||||
// Clear all timeouts
|
||||
this.timeoutQueue.forEach(timeoutObject => {
|
||||
if (timeoutObject.timeoutId !== null) {
|
||||
clearTimeout(timeoutObject.timeoutId);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear queue
|
||||
this.timeoutQueue = [];
|
||||
|
||||
// Reset delay
|
||||
this.delay = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the animation speed
|
||||
* @param {number} speed - Animation speed factor (lower is faster)
|
||||
*/
|
||||
setSpeed(speed) {
|
||||
if (typeof speed !== 'number' || speed <= 0) {
|
||||
console.error('Animation Queue: Invalid speed value');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update config using parent's updateConfig method
|
||||
this.updateConfig({ speed });
|
||||
console.log(`Animation Queue: Speed set to ${speed}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current animation speed
|
||||
* @returns {number} - Current animation speed factor
|
||||
*/
|
||||
getSpeed() {
|
||||
return this.config.speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if fast forwarding is active
|
||||
* @returns {boolean} - Whether fast forwarding is active
|
||||
*/
|
||||
isFastForwarding() {
|
||||
return this.config.fastForwardEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current queue length
|
||||
* @returns {number} - Number of items in the queue
|
||||
*/
|
||||
getQueueLength() {
|
||||
return this.timeoutQueue.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current accumulated delay
|
||||
* @returns {number} - Current delay in milliseconds
|
||||
*/
|
||||
getCurrentDelay() {
|
||||
return this.delay;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the singleton instance
|
||||
const AnimationQueue = new AnimationQueueModule();
|
||||
|
||||
// Export the module
|
||||
export { AnimationQueue };
|
||||
Reference in New Issue
Block a user