Checkpoint current UI and ink integration state

This commit is contained in:
2026-05-18 02:46:02 +02:00
parent 2c54498ee2
commit d7bb175167
384 changed files with 922883 additions and 764 deletions
+83 -15
View File
@@ -14,11 +14,13 @@ class UIInputHandlerModule extends BaseModule {
this.commandHistoryElement = null;
// Input state
this.inputEnabled = true;
this.inputEnabled = false;
this.historyIndex = -1;
this.commandHistory = [];
this.inputBuffer = '';
this.inputMode = 'text';
this.inputMode = 'none';
this.cursorAnimationTimer = null;
this.cursorAnimationFrame = 0;
// Bind methods using the parent class bindMethods utility
this.bindMethods([
@@ -37,6 +39,10 @@ class UIInputHandlerModule extends BaseModule {
'setProcessState',
'setInputAvailability',
'setMode',
'setInputModeDataset',
'installMouseCursors',
'startMouseCursorAnimation',
'stopMouseCursorAnimation',
'clearHistory'
]);
@@ -62,11 +68,12 @@ class UIInputHandlerModule extends BaseModule {
this.reportProgress(60, 'Setting up input elements');
this.setupInputElements();
this.installMouseCursors();
this.addEventListener(document, 'story:process-state', (event) => {
this.setProcessState(event.detail?.state || 'ready', event.detail || {});
});
this.addEventListener(document, 'story:input-mode', (event) => {
this.setMode(event.detail || 'text');
this.setMode(event.detail || 'none');
});
this.addEventListener(document, 'story:turn-start', (event) => {
this.bindHistoryToTurn(event.detail?.turnId);
@@ -157,6 +164,12 @@ class UIInputHandlerModule extends BaseModule {
pageLeft.appendChild(choicesContainer);
}
if (!document.getElementById('left_control_separator')) {
const controlSeparator = document.createElement('div');
controlSeparator.id = 'left_control_separator';
choicesContainer.insertBefore(controlSeparator, choicesContainer.firstChild);
}
// Create command history container if needed
let commandHistory = document.getElementById('command_history');
@@ -227,10 +240,8 @@ class UIInputHandlerModule extends BaseModule {
// Position the cursor
if (playerInput && cursor) {
this.positionCursor(playerInput, cursor);
this.setProcessState('ready', { reason: 'input-initialized' });
this.focusInput();
requestAnimationFrame(() => this.focusInput());
setTimeout(() => this.focusInput(), 250);
this.setInputModeDataset();
this.setInputAvailability(false);
}
console.log('UIInputHandler: Input elements setup complete');
@@ -312,8 +323,14 @@ class UIInputHandlerModule extends BaseModule {
}
setMode(mode) {
this.inputMode = ['text', 'choice', 'end'].includes(mode) ? mode : 'text';
this.setInputAvailability(this.inputMode === 'text');
this.inputMode = ['text', 'choice', 'end', 'none'].includes(mode) ? mode : 'none';
this.setInputModeDataset();
const state = document.documentElement.dataset.processState || 'loading';
this.setInputAvailability(this.inputMode === 'text' && state === 'ready');
}
setInputModeDataset() {
document.documentElement.dataset.inputMode = this.inputMode || 'none';
}
applyMouseCursor(state) {
@@ -326,28 +343,79 @@ class UIInputHandlerModule extends BaseModule {
const cursor = this.getMouseCursor(state);
if (cursor) {
root.style.setProperty('--process-cursor', cursor);
this.startMouseCursorAnimation(state);
} else {
root.style.removeProperty('--process-cursor');
this.stopMouseCursorAnimation();
}
}
installMouseCursors() {
const root = document.documentElement;
if (!root) return;
root.style.setProperty('--default-cursor', this.buildMouseCursor('default', 'default', 4, 3));
root.style.setProperty('--pointer-cursor', this.buildMouseCursor('pointer', 'pointer', 7, 2));
}
getMouseCursor(state) {
if (state === 'ready') {
return '';
}
const svg = this.getMouseCursorSvg(state);
const fallback = state === 'command-waiting' ? 'wait' : 'progress';
return `url("${this.toCursorDataUrl(svg)}") 12 12, ${fallback}`;
return this.buildMouseCursor(state, fallback, 12, 12, this.cursorAnimationFrame);
}
getMouseCursorSvg(state) {
buildMouseCursor(state, fallback = 'default', hotspotX = 12, hotspotY = 12, frame = 0) {
const svg = this.getMouseCursorSvg(state, frame);
return `url("${this.toCursorDataUrl(svg)}") ${hotspotX} ${hotspotY}, ${fallback}`;
}
startMouseCursorAnimation(state) {
const animatedStates = new Set(['command-waiting', 'waiting-generating', 'playing-generating']);
if (!animatedStates.has(state)) {
this.stopMouseCursorAnimation();
return;
}
if (this.cursorAnimationTimer) return;
this.cursorAnimationTimer = window.setInterval(() => {
const currentState = document.documentElement.dataset.processState || 'ready';
if (!animatedStates.has(currentState)) {
this.stopMouseCursorAnimation();
return;
}
this.cursorAnimationFrame = (this.cursorAnimationFrame + 1) % 8;
document.documentElement.style.setProperty('--process-cursor', this.getMouseCursor(currentState));
}, 160);
}
stopMouseCursorAnimation() {
if (this.cursorAnimationTimer) {
window.clearInterval(this.cursorAnimationTimer);
this.cursorAnimationTimer = null;
}
this.cursorAnimationFrame = 0;
}
getMouseCursorSvg(state, frame = 0) {
const stroke = '#222222';
const common = `xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"`;
const spinnerSpokes = Array.from({ length: 8 }, (_, index) => {
const opacity = 0.25 + (((index + frame) % 8) / 7) * 0.75;
const angle = (index * 45) * Math.PI / 180;
const x1 = 12 + Math.cos(angle) * 6;
const y1 = 12 + Math.sin(angle) * 6;
const x2 = 12 + Math.cos(angle) * 9;
const y2 = 12 + Math.sin(angle) * 9;
return `<path opacity="${opacity.toFixed(2)}" d="M${x1.toFixed(2)} ${y1.toFixed(2)} ${x2.toFixed(2)} ${y2.toFixed(2)}"/>`;
}).join('');
const sandTop = frame % 4 < 2 ? '<path d="M9 5h6"/><path d="M10 8h4"/>' : '<path d="M10 16h4"/><path d="M9 19h6"/>';
const icons = {
'command-waiting': `<svg ${common}><path d="M5 22h14"/><path d="M5 2h14"/><path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/><path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/></svg>`,
'waiting-generating': `<svg ${common}><path d="M12 2v4"/><path d="M12 18v4"/><path d="m4.93 4.93 2.83 2.83"/><path d="m16.24 16.24 2.83 2.83"/><path d="M2 12h4"/><path d="M18 12h4"/><path d="m4.93 19.07 2.83-2.83"/><path d="m16.24 7.76 2.83-2.83"/></svg>`,
'playing-generating': `<svg ${common}><path d="M11 5 6 9H2v6h4l5 4z"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M12 2v3"/><path d="M12 19v3"/></svg>`,
'default': `<svg ${common}><path d="M4 3l7.5 18 2.1-7.4L21 11z"/><path d="M13.6 13.6 18 18"/></svg>`,
'pointer': `<svg ${common}><path d="M8 11V4a2 2 0 1 1 4 0v6"/><path d="M12 10V7a2 2 0 1 1 4 0v5"/><path d="M16 12v-1a2 2 0 1 1 4 0v3a7 7 0 0 1-7 7h-1a6 6 0 0 1-5-2.7L3 13a2 2 0 0 1 3-2.6l2 2.1"/></svg>`,
'command-waiting': `<svg ${common}><path d="M5 22h14"/><path d="M5 2h14"/><path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/><path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/>${sandTop}</svg>`,
'waiting-generating': `<svg ${common}>${spinnerSpokes}</svg>`,
'playing-generating': `<svg ${common}><path d="M11 5 6 9H2v6h4l5 4z"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>${spinnerSpokes}</svg>`,
'playing-ready': `<svg ${common}><path d="M11 5 6 9H2v6h4l5 4z"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>`
};