Add question-mark help viewer mode backed by help JSON
This commit is contained in:
@@ -29,7 +29,7 @@
|
|||||||
height="600"
|
height="600"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
aria-label="Chat Grid, press arrows to move."
|
aria-label="Chat Grid, press question mark for help."
|
||||||
></canvas>
|
></canvas>
|
||||||
<div id="instructions" class="hidden"></div>
|
<div id="instructions" class="hidden"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"title": "Movement",
|
"title": "Movement",
|
||||||
"items": [
|
"items": [
|
||||||
{ "keys": "Arrow Keys", "description": "Move" },
|
{ "keys": "Arrow Keys", "description": "Move" },
|
||||||
|
{ "keys": "Question Mark", "description": "Open help viewer" },
|
||||||
{ "keys": "C", "description": "Speak coordinates" },
|
{ "keys": "C", "description": "Speak coordinates" },
|
||||||
{ "keys": "Escape", "description": "Disconnect/cancel" }
|
{ "keys": "Escape", "description": "Disconnect/cancel" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.21 R105";
|
window.CHGRID_WEB_VERSION = "2026.02.21 R106";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const itemEmitRuntime = new ItemEmitRuntime(audio, resolveIncomingSoundUrl);
|
|||||||
let internalClipboardText = '';
|
let internalClipboardText = '';
|
||||||
let replaceTextOnNextType = false;
|
let replaceTextOnNextType = false;
|
||||||
let pendingEscapeDisconnect = false;
|
let pendingEscapeDisconnect = false;
|
||||||
|
let helpViewerLines: string[] = [];
|
||||||
|
let helpViewerIndex = 0;
|
||||||
let audioLayers: AudioLayerState = {
|
let audioLayers: AudioLayerState = {
|
||||||
voice: true,
|
voice: true,
|
||||||
item: true,
|
item: true,
|
||||||
@@ -313,6 +315,7 @@ function setUpdatesExpanded(expanded: boolean): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderHelp(help: HelpData): void {
|
function renderHelp(help: HelpData): void {
|
||||||
|
const lines: string[] = [];
|
||||||
dom.instructions.innerHTML = '';
|
dom.instructions.innerHTML = '';
|
||||||
const heading = document.createElement('h2');
|
const heading = document.createElement('h2');
|
||||||
heading.textContent = 'Help';
|
heading.textContent = 'Help';
|
||||||
@@ -321,6 +324,7 @@ function renderHelp(help: HelpData): void {
|
|||||||
const sectionHeading = document.createElement('h3');
|
const sectionHeading = document.createElement('h3');
|
||||||
sectionHeading.textContent = section.title;
|
sectionHeading.textContent = section.title;
|
||||||
dom.instructions.appendChild(sectionHeading);
|
dom.instructions.appendChild(sectionHeading);
|
||||||
|
lines.push(section.title);
|
||||||
for (const item of section.items) {
|
for (const item of section.items) {
|
||||||
const line = document.createElement('p');
|
const line = document.createElement('p');
|
||||||
const keys = document.createElement('b');
|
const keys = document.createElement('b');
|
||||||
@@ -328,8 +332,11 @@ function renderHelp(help: HelpData): void {
|
|||||||
line.appendChild(keys);
|
line.appendChild(keys);
|
||||||
line.append(` ${item.description}`);
|
line.append(` ${item.description}`);
|
||||||
dom.instructions.appendChild(line);
|
dom.instructions.appendChild(line);
|
||||||
|
lines.push(`${item.keys}: ${item.description}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
helpViewerLines = lines;
|
||||||
|
helpViewerIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadHelp(): Promise<void> {
|
async function loadHelp(): Promise<void> {
|
||||||
@@ -581,6 +588,18 @@ function itemPropertyLabel(key: string): string {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openHelpViewer(): void {
|
||||||
|
if (helpViewerLines.length === 0) {
|
||||||
|
updateStatus('Help unavailable.');
|
||||||
|
audio.sfxUiCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.mode = 'helpView';
|
||||||
|
helpViewerIndex = 0;
|
||||||
|
updateStatus(helpViewerLines[helpViewerIndex]);
|
||||||
|
audio.sfxUiBlip();
|
||||||
|
}
|
||||||
|
|
||||||
function getItemsAtPosition(x: number, y: number): WorldItem[] {
|
function getItemsAtPosition(x: number, y: number): WorldItem[] {
|
||||||
return Array.from(state.items.values()).filter((item) => !item.carrierId && item.x === x && item.y === y);
|
return Array.from(state.items.values()).filter((item) => !item.carrierId && item.x === x && item.y === y);
|
||||||
}
|
}
|
||||||
@@ -1533,6 +1552,11 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (code === 'Slash' && shiftKey) {
|
||||||
|
openHelpViewer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (code === 'Slash' && !shiftKey) {
|
if (code === 'Slash' && !shiftKey) {
|
||||||
state.mode = 'chat';
|
state.mode = 'chat';
|
||||||
state.nicknameInput = '';
|
state.nicknameInput = '';
|
||||||
@@ -1574,6 +1598,45 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleHelpViewModeInput(code: string): void {
|
||||||
|
if (helpViewerLines.length === 0) {
|
||||||
|
state.mode = 'normal';
|
||||||
|
updateStatus('Help unavailable.');
|
||||||
|
audio.sfxUiCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code === 'ArrowDown') {
|
||||||
|
helpViewerIndex = Math.min(helpViewerLines.length - 1, helpViewerIndex + 1);
|
||||||
|
updateStatus(helpViewerLines[helpViewerIndex]);
|
||||||
|
audio.sfxUiBlip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code === 'ArrowUp') {
|
||||||
|
helpViewerIndex = Math.max(0, helpViewerIndex - 1);
|
||||||
|
updateStatus(helpViewerLines[helpViewerIndex]);
|
||||||
|
audio.sfxUiBlip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code === 'Home') {
|
||||||
|
helpViewerIndex = 0;
|
||||||
|
updateStatus(helpViewerLines[helpViewerIndex]);
|
||||||
|
audio.sfxUiBlip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code === 'End') {
|
||||||
|
helpViewerIndex = helpViewerLines.length - 1;
|
||||||
|
updateStatus(helpViewerLines[helpViewerIndex]);
|
||||||
|
audio.sfxUiBlip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code === 'Escape') {
|
||||||
|
state.mode = 'normal';
|
||||||
|
updateStatus('Closed help.');
|
||||||
|
audio.sfxUiCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleChatModeInput(code: string, key: string, ctrlKey: boolean): void {
|
function handleChatModeInput(code: string, key: string, ctrlKey: boolean): void {
|
||||||
if (code === 'Enter') {
|
if (code === 'Enter') {
|
||||||
const message = state.nicknameInput.trim();
|
const message = state.nicknameInput.trim();
|
||||||
@@ -2220,6 +2283,8 @@ function setupInputHandlers(): void {
|
|||||||
handleChatModeInput(code, event.key, event.ctrlKey);
|
handleChatModeInput(code, event.key, event.ctrlKey);
|
||||||
} else if (state.mode === 'effectSelect') {
|
} else if (state.mode === 'effectSelect') {
|
||||||
handleEffectSelectModeInput(code, event.key);
|
handleEffectSelectModeInput(code, event.key);
|
||||||
|
} else if (state.mode === 'helpView') {
|
||||||
|
handleHelpViewModeInput(code);
|
||||||
} else if (state.mode === 'listUsers') {
|
} else if (state.mode === 'listUsers') {
|
||||||
handleListModeInput(code, event.key);
|
handleListModeInput(code, event.key);
|
||||||
} else if (state.mode === 'listItems') {
|
} else if (state.mode === 'listItems') {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type SelectionContext = 'pickup' | 'drop' | 'delete' | 'edit' | 'use' | '
|
|||||||
|
|
||||||
export type GameMode =
|
export type GameMode =
|
||||||
| 'normal'
|
| 'normal'
|
||||||
|
| 'helpView'
|
||||||
| 'nickname'
|
| 'nickname'
|
||||||
| 'chat'
|
| 'chat'
|
||||||
| 'effectSelect'
|
| 'effectSelect'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ This document is the authoritative keymap for the client.
|
|||||||
|
|
||||||
### Movement
|
### Movement
|
||||||
- `Arrow Keys`: Move
|
- `Arrow Keys`: Move
|
||||||
|
- `?`: Open help viewer
|
||||||
- `C`: Speak coordinates
|
- `C`: Speak coordinates
|
||||||
- `Escape`: Press once for disconnect prompt, press again to disconnect
|
- `Escape`: Press once for disconnect prompt, press again to disconnect
|
||||||
|
|
||||||
@@ -61,3 +62,10 @@ Applies to effect select, user/item list modes, item selection, item property li
|
|||||||
- `Enter`: Confirm selection
|
- `Enter`: Confirm selection
|
||||||
- `Escape`: Exit/cancel
|
- `Escape`: Exit/cancel
|
||||||
- First-letter navigation: jump to next matching entry
|
- First-letter navigation: jump to next matching entry
|
||||||
|
|
||||||
|
## Help Viewer Mode
|
||||||
|
|
||||||
|
- `ArrowUp` / `ArrowDown`: Previous/next help line
|
||||||
|
- `Home` / `End`: First/last help line
|
||||||
|
- `Escape`: Exit help viewer
|
||||||
|
- No first-letter navigation in this mode
|
||||||
|
|||||||
Reference in New Issue
Block a user