Add ink integration UI and media playback
This commit is contained in:
@@ -7,7 +7,7 @@ class UIControllerModule extends BaseModule {
|
||||
|
||||
// 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', 'sentence-queue', 'playback-coordinator'];
|
||||
this.dependencies = ['animation-queue', 'ui-display-handler', 'ui-input-handler', 'ui-effects', 'text-buffer', 'socket-client', 'sentence-queue', 'playback-coordinator', 'persistence-manager'];
|
||||
|
||||
// References to sub-modules
|
||||
this.displayHandler = null;
|
||||
@@ -47,6 +47,8 @@ class UIControllerModule extends BaseModule {
|
||||
'syncTopControls',
|
||||
'getStoredTtsPreference',
|
||||
'setStoredTtsPreference',
|
||||
'getStoredAppPreference',
|
||||
'setStoredAppPreference',
|
||||
'sliderValueFromSpeed',
|
||||
'speedFromSliderValue',
|
||||
'initializeTextBuffer',
|
||||
@@ -267,7 +269,8 @@ class UIControllerModule extends BaseModule {
|
||||
}
|
||||
|
||||
const playbackCoordinator = this.getModule('playback-coordinator');
|
||||
if (playbackCoordinator && playbackCoordinator.isPlaying) {
|
||||
const hasSkippablePause = document.documentElement.dataset.skippablePause === 'true';
|
||||
if ((playbackCoordinator && playbackCoordinator.isPlaying) || hasSkippablePause) {
|
||||
this.handleCommand({ type: 'continue', source: 'book-click' });
|
||||
}
|
||||
|
||||
@@ -350,6 +353,9 @@ class UIControllerModule extends BaseModule {
|
||||
document.addEventListener('preference-updated', (event) => {
|
||||
const { category, key, value } = event.detail || {};
|
||||
if (category !== 'tts') {
|
||||
if (category === 'app' && key === 'autoplay') {
|
||||
this.syncTopControls();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -391,6 +397,7 @@ class UIControllerModule extends BaseModule {
|
||||
|
||||
bindTopControls() {
|
||||
const speechToggle = document.getElementById('speech');
|
||||
const autoplayToggle = document.getElementById('autoplay');
|
||||
const speedSlider = document.getElementById('speed');
|
||||
const speedReset = document.getElementById('speed_reset');
|
||||
|
||||
@@ -428,6 +435,22 @@ class UIControllerModule extends BaseModule {
|
||||
});
|
||||
}
|
||||
|
||||
if (autoplayToggle && autoplayToggle.dataset.uiControllerBound !== 'true') {
|
||||
autoplayToggle.dataset.uiControllerBound = 'true';
|
||||
autoplayToggle.removeAttribute('disabled');
|
||||
autoplayToggle.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const nextAutoplay = !this.getStoredAppPreference('autoplay', true);
|
||||
this.setStoredAppPreference('autoplay', nextAutoplay);
|
||||
console.log(`UIController: Autoplay set to ${nextAutoplay ? 'enabled' : 'disabled'}`);
|
||||
this.syncTopControls();
|
||||
document.dispatchEvent(new CustomEvent('app:autoplay:change', {
|
||||
detail: { enabled: nextAutoplay, source: 'topbar' }
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if (speedSlider && speedSlider.dataset.uiControllerBound !== 'true') {
|
||||
speedSlider.dataset.uiControllerBound = 'true';
|
||||
speedSlider.min = speedSlider.min || '50';
|
||||
@@ -518,6 +541,48 @@ class UIControllerModule extends BaseModule {
|
||||
console.warn('UIController: Failed to write TTS preference fallback:', error);
|
||||
}
|
||||
}
|
||||
|
||||
getStoredAppPreference(key, defaultValue) {
|
||||
const persistenceManager = this.getModule('persistence-manager');
|
||||
if (persistenceManager && typeof persistenceManager.getPreference === 'function') {
|
||||
const value = persistenceManager.getPreference('app', key, undefined);
|
||||
if (typeof value !== 'undefined' && value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem('ai-interactive-fiction-preferences');
|
||||
if (raw) {
|
||||
const prefs = JSON.parse(raw);
|
||||
if (prefs && prefs.app && Object.prototype.hasOwnProperty.call(prefs.app, key)) {
|
||||
return prefs.app[key];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('UIController: Failed to read app preference fallback:', error);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
setStoredAppPreference(key, value) {
|
||||
const persistenceManager = this.getModule('persistence-manager');
|
||||
if (persistenceManager && typeof persistenceManager.updatePreference === 'function') {
|
||||
persistenceManager.updatePreference('app', key, value);
|
||||
}
|
||||
|
||||
try {
|
||||
const storageKey = 'ai-interactive-fiction-preferences';
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
const prefs = raw ? JSON.parse(raw) : {};
|
||||
prefs.app = prefs.app || {};
|
||||
prefs.app[key] = value;
|
||||
localStorage.setItem(storageKey, JSON.stringify(prefs));
|
||||
} catch (error) {
|
||||
console.warn('UIController: Failed to write app preference fallback:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async setupMainUI() {
|
||||
// Ensure all UI components exist
|
||||
@@ -583,6 +648,9 @@ class UIControllerModule extends BaseModule {
|
||||
break;
|
||||
case 'continue':
|
||||
{
|
||||
document.dispatchEvent(new CustomEvent('ui:command', {
|
||||
detail: { moduleId: this.id, type: 'continue', source: command.source || 'ui-controller-forward' }
|
||||
}));
|
||||
const playbackCoordinator = this.getModule('playback-coordinator');
|
||||
if (playbackCoordinator && playbackCoordinator.isPlaying) {
|
||||
playbackCoordinator.fastForward();
|
||||
@@ -633,6 +701,7 @@ class UIControllerModule extends BaseModule {
|
||||
const loadButton = document.getElementById('reload');
|
||||
const restartButton = document.getElementById('rewind');
|
||||
const speechToggle = document.getElementById('speech');
|
||||
const autoplayToggle = document.getElementById('autoplay');
|
||||
|
||||
// Update save button state
|
||||
if (saveButton && typeof canSave === 'boolean') {
|
||||
@@ -679,6 +748,13 @@ class UIControllerModule extends BaseModule {
|
||||
speechToggle.title = 'Enable speech';
|
||||
}
|
||||
}
|
||||
|
||||
if (autoplayToggle) {
|
||||
const autoplay = this.getStoredAppPreference('autoplay', true) !== false;
|
||||
autoplayToggle.removeAttribute('disabled');
|
||||
autoplayToggle.style.fontWeight = autoplay ? 'bold' : 'normal';
|
||||
autoplayToggle.style.color = autoplay ? '#000' : '#999';
|
||||
}
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
|
||||
Reference in New Issue
Block a user