Document markup and improve choice tags
This commit is contained in:
@@ -48,6 +48,10 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.lastManualScrollAt = 0;
|
||||
this.layoutFlowLine = 0;
|
||||
this.layoutExclusions = [];
|
||||
this.notificationQueue = [];
|
||||
this.notificationActive = false;
|
||||
this.pendingTerminalNotifications = [];
|
||||
this.latestInputMode = 'text';
|
||||
|
||||
// Resources to preload
|
||||
this.cssPath = '/css/style.css';
|
||||
@@ -121,7 +125,19 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
'measureText',
|
||||
'loadCSS',
|
||||
'showChoices',
|
||||
'preloadImages'
|
||||
'preloadImages',
|
||||
'createCreditsDialog',
|
||||
'openCreditsDialog',
|
||||
'closeCreditsDialog',
|
||||
'loadCreditsText',
|
||||
'createNotificationDialog',
|
||||
'handleStoryTag',
|
||||
'getTagMessage',
|
||||
'showNotification',
|
||||
'displayNextNotification',
|
||||
'queueTerminalNotification',
|
||||
'flushTerminalNotifications',
|
||||
'closeNotification'
|
||||
]);
|
||||
|
||||
console.log('UIDisplayHandler: Constructor initialized');
|
||||
@@ -173,6 +189,15 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.addEventListener(document, 'story:history-updated', (event) => {
|
||||
this.updateStoryScrollbar(event.detail || {});
|
||||
});
|
||||
this.addEventListener(document, 'story:tag', (event) => {
|
||||
this.handleStoryTag(event.detail);
|
||||
});
|
||||
this.addEventListener(document, 'story:turn-start', () => {
|
||||
this.latestInputMode = 'text';
|
||||
});
|
||||
this.addEventListener(document, 'story:input-mode', (event) => {
|
||||
this.latestInputMode = event.detail || 'text';
|
||||
});
|
||||
this.addEventListener(document, 'wheel', this.handleHistoryWheel, { passive: false });
|
||||
this.addEventListener(document, 'keydown', (event) => {
|
||||
const tagName = String(event.target?.tagName || '').toLowerCase();
|
||||
@@ -213,6 +238,9 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
? this.t('title.continueHint')
|
||||
: this.t('title.fastForwardHint');
|
||||
}
|
||||
if (state === 'ready' && this.latestInputMode === 'end') {
|
||||
this.flushTerminalNotifications();
|
||||
}
|
||||
});
|
||||
|
||||
if (window.ResizeObserver && this.paragraphContainer) {
|
||||
@@ -472,6 +500,9 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
lighting.id = 'lighting';
|
||||
document.body.appendChild(lighting);
|
||||
}
|
||||
|
||||
this.createCreditsDialog();
|
||||
this.createNotificationDialog();
|
||||
|
||||
console.log('UIDisplayHandler: All containers initialized');
|
||||
this.applyGameConfig(this.gameConfig?.getConfig?.());
|
||||
@@ -497,7 +528,27 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
metadata.version ? this.t('title.version', { version: metadata.version }) : '',
|
||||
metadata.copyright || ''
|
||||
].filter(Boolean);
|
||||
legalElement.textContent = items.join(' · ');
|
||||
legalElement.innerHTML = '';
|
||||
const legalText = document.createElement('span');
|
||||
legalText.id = 'game_legal_text';
|
||||
legalText.textContent = items.join(' | ');
|
||||
legalElement.appendChild(legalText);
|
||||
|
||||
const creditsButton = document.createElement('button');
|
||||
creditsButton.id = 'credits_button';
|
||||
creditsButton.type = 'button';
|
||||
creditsButton.textContent = this.t('credits.button');
|
||||
creditsButton.title = this.t('credits.buttonTitle');
|
||||
creditsButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.openCreditsDialog();
|
||||
});
|
||||
|
||||
if (items.length > 0) {
|
||||
legalElement.appendChild(document.createTextNode(' | '));
|
||||
}
|
||||
legalElement.appendChild(creditsButton);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,6 +573,9 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
setText('options', 'topbar.options');
|
||||
setText('remark_text', 'title.fastForwardHint');
|
||||
setText('start_prompt', 'title.startPrompt');
|
||||
setText('credits_dialog_title', 'credits.title');
|
||||
setText('credits_close', 'credits.close');
|
||||
setText('story_popup_ok', 'popup.ok');
|
||||
setTitle('speech', 'topbar.speechTitle');
|
||||
setTitle('autoplay', 'topbar.autoplayTitle');
|
||||
setTitle('rewind', 'topbar.newGameTitle');
|
||||
@@ -533,6 +587,224 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
if (input) input.setAttribute('placeholder', this.t('input.placeholder'));
|
||||
this.applyGameConfig(this.gameConfig?.getConfig?.());
|
||||
}
|
||||
|
||||
createCreditsDialog() {
|
||||
if (document.getElementById('credits_modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'credits_modal';
|
||||
modal.className = 'credits-modal';
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
modal.innerHTML = `
|
||||
<div class="credits-dialog" role="dialog" aria-modal="true" aria-labelledby="credits_dialog_title">
|
||||
<div class="credits-dialog-header">
|
||||
<h2 id="credits_dialog_title"></h2>
|
||||
<button type="button" id="credits_close"></button>
|
||||
</div>
|
||||
<div class="credits-logo-row" aria-label="Credits links">
|
||||
<a href="https://openai.com/" target="_blank" rel="noreferrer"><img src="https://cdn.simpleicons.org/openai/2b2218" alt="OpenAI"></a>
|
||||
<a href="https://www.inklestudios.com/ink/" target="_blank" rel="noreferrer" class="credits-wordmark">ink</a>
|
||||
<a href="https://mnater.github.io/Hyphenopoly/" target="_blank" rel="noreferrer" class="credits-wordmark">Hyphenopoly</a>
|
||||
<a href="https://github.com/hexgrad/kokoro" target="_blank" rel="noreferrer" class="credits-wordmark">Kokoro</a>
|
||||
<a href="https://suno.com/" target="_blank" rel="noreferrer" class="credits-wordmark">Suno</a>
|
||||
</div>
|
||||
<pre id="credits_content" class="credits-content"></pre>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
modal.addEventListener('click', (event) => {
|
||||
if (event.target === modal) {
|
||||
this.closeCreditsDialog();
|
||||
}
|
||||
});
|
||||
|
||||
const closeButton = document.getElementById('credits_close');
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => this.closeCreditsDialog());
|
||||
}
|
||||
}
|
||||
|
||||
async openCreditsDialog() {
|
||||
const modal = document.getElementById('credits_modal');
|
||||
const content = document.getElementById('credits_content');
|
||||
if (!modal || !content) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.classList.add('visible');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
|
||||
if (!content.dataset.loaded) {
|
||||
content.textContent = this.t('credits.loading');
|
||||
content.textContent = await this.loadCreditsText();
|
||||
content.dataset.loaded = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
closeCreditsDialog() {
|
||||
const modal = document.getElementById('credits_modal');
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
modal.classList.remove('visible');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
async loadCreditsText() {
|
||||
try {
|
||||
const response = await fetch('/THIRD_PARTY_NOTICES.md', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.warn('UIDisplayHandler: Failed to load credits notices', error);
|
||||
return this.t('credits.loadFailed');
|
||||
}
|
||||
}
|
||||
|
||||
createNotificationDialog() {
|
||||
if (document.getElementById('story_popup_modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'story_popup_modal';
|
||||
modal.className = 'story-popup-modal';
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
modal.innerHTML = `
|
||||
<div class="story-popup-dialog" role="dialog" aria-modal="true" aria-labelledby="story_popup_title">
|
||||
<h2 id="story_popup_title"></h2>
|
||||
<div id="story_popup_message"></div>
|
||||
<button type="button" id="story_popup_ok"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
modal.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.target === modal) {
|
||||
this.closeNotification();
|
||||
}
|
||||
});
|
||||
modal.addEventListener('pointerdown', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
const okButton = document.getElementById('story_popup_ok');
|
||||
if (okButton) {
|
||||
okButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.closeNotification();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleStoryTag(tag) {
|
||||
const key = String(tag?.key || '').toLowerCase();
|
||||
if (!['score', 'error', 'achievement', 'alert'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = this.getTagMessage(tag);
|
||||
if (key === 'score') {
|
||||
this.queueTerminalNotification(
|
||||
'ending',
|
||||
this.t('popup.endingTitle'),
|
||||
message || this.t('popup.defaultEnding')
|
||||
);
|
||||
} else if (key === 'error') {
|
||||
this.queueTerminalNotification(
|
||||
'error',
|
||||
this.t('popup.errorTitle'),
|
||||
message || this.t('popup.defaultError')
|
||||
);
|
||||
} else if (key === 'achievement') {
|
||||
this.showNotification(
|
||||
'achievement',
|
||||
this.t('popup.achievementTitle'),
|
||||
message || this.t('popup.defaultAchievement')
|
||||
);
|
||||
} else if (key === 'alert') {
|
||||
this.showNotification(
|
||||
'alert',
|
||||
this.t('popup.alertTitle'),
|
||||
message || this.t('popup.defaultAlert')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTagMessage(tag) {
|
||||
return [tag?.value, tag?.param]
|
||||
.map((part) => String(part || '').trim())
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
showNotification(kind, title, message) {
|
||||
this.notificationQueue.push({ kind, title, message });
|
||||
this.displayNextNotification();
|
||||
}
|
||||
|
||||
queueTerminalNotification(kind, title, message) {
|
||||
this.pendingTerminalNotifications.push({ kind, title, message });
|
||||
if (this.latestInputMode === 'end') {
|
||||
this.flushTerminalNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
flushTerminalNotifications() {
|
||||
if (this.pendingTerminalNotifications.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.pendingTerminalNotifications.splice(0).forEach((notification) => {
|
||||
this.showNotification(notification.kind, notification.title, notification.message);
|
||||
});
|
||||
}
|
||||
|
||||
displayNextNotification() {
|
||||
if (this.notificationActive || this.notificationQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = this.notificationQueue.shift();
|
||||
const modal = document.getElementById('story_popup_modal');
|
||||
const title = document.getElementById('story_popup_title');
|
||||
const message = document.getElementById('story_popup_message');
|
||||
const okButton = document.getElementById('story_popup_ok');
|
||||
if (!modal || !title || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.dataset.kind = next.kind;
|
||||
title.textContent = next.title;
|
||||
message.textContent = next.message;
|
||||
if (okButton) {
|
||||
okButton.textContent = this.t('popup.ok');
|
||||
setTimeout(() => okButton.focus(), 0);
|
||||
}
|
||||
this.notificationActive = true;
|
||||
modal.classList.add('visible');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
closeNotification() {
|
||||
const modal = document.getElementById('story_popup_modal');
|
||||
if (!modal) {
|
||||
this.notificationActive = false;
|
||||
return;
|
||||
}
|
||||
modal.classList.remove('visible');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
this.notificationActive = false;
|
||||
setTimeout(() => this.displayNextNotification(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure text width using canvas
|
||||
@@ -1927,6 +2199,11 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.container.appendChild(this.paragraphContainer);
|
||||
}
|
||||
this.renderedItems = [];
|
||||
this.notificationQueue = [];
|
||||
this.pendingTerminalNotifications = [];
|
||||
this.notificationActive = false;
|
||||
document.getElementById('story_popup_modal')?.classList.remove('visible');
|
||||
document.getElementById('story_popup_modal')?.setAttribute('aria-hidden', 'true');
|
||||
this.historyWindowStartId = 1;
|
||||
this.historyWindowEndId = 0;
|
||||
this.storyTopLine = 0;
|
||||
|
||||
Reference in New Issue
Block a user