Add piano-mode help viewer bound to question mark

This commit is contained in:
Jage9
2026-02-23 01:08:50 -05:00
parent 4022a66163
commit 446202288f
5 changed files with 91 additions and 9 deletions

View File

@@ -87,7 +87,7 @@
}, },
{ {
"keys": "Piano mode", "keys": "Piano mode",
"description": "When using a piano: 1-9 (and 0 for the 10th slot) changes instrument, -/= changes octave, ASDFGHJKL;' plays C major notes, WETYUOP] plays sharps, Z starts/stops recording, X plays recording, C stops playback, Escape exits" "description": "When using a piano: press question mark for piano help. 1-9 (and 0 for the 10th slot) changes instrument, -/= changes octave, ASDFGHJKL;' plays C major notes, WETYUOP] plays sharps, Z starts/stops recording, X plays recording, C stops playback, Escape exits"
} }
] ]
}, },

41
client/public/piano.json Normal file
View File

@@ -0,0 +1,41 @@
{
"sections": [
{
"title": "Piano Mode",
"items": [
{
"keys": "A S D F G H J K L ; '",
"description": "Play white keys (C major scale from C4)."
},
{
"keys": "W E T Y U O P ]",
"description": "Play sharp keys."
},
{
"keys": "1-9, 0",
"description": "Change instrument presets."
},
{
"keys": "- / =",
"description": "Shift octave down or up."
},
{
"keys": "Z",
"description": "Start or stop recording (up to 30 seconds)."
},
{
"keys": "X",
"description": "Play the saved recording."
},
{
"keys": "C",
"description": "Stop recording playback."
},
{
"keys": "Escape",
"description": "Exit piano mode."
}
]
}
]
}

View File

@@ -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.22 R208"; window.CHGRID_WEB_VERSION = "2026.02.22 R209";
// 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";

View File

@@ -49,6 +49,7 @@ import {
getDirection, getDirection,
getNearestItem, getNearestItem,
getNearestPeer, getNearestPeer,
type GameMode,
type WorldItem, type WorldItem,
} from './state/gameState'; } from './state/gameState';
import { import {
@@ -188,6 +189,18 @@ type HelpData = {
sections: HelpSection[]; sections: HelpSection[];
}; };
/** Builds linearized help-view lines from sectioned help content. */
function buildHelpLines(help: HelpData): string[] {
const lines: string[] = [];
for (const section of help.sections) {
lines.push(section.title);
for (const item of section.items) {
lines.push(`${item.keys}: ${item.description}`);
}
}
return lines;
}
const APP_VERSION = String(window.CHGRID_WEB_VERSION ?? '').trim(); const APP_VERSION = String(window.CHGRID_WEB_VERSION ?? '').trim();
const DISPLAY_TIME_ZONE = resolveDisplayTimeZone(); const DISPLAY_TIME_ZONE = resolveDisplayTimeZone();
dom.appVersion.textContent = APP_VERSION dom.appVersion.textContent = APP_VERSION
@@ -230,8 +243,11 @@ let internalClipboardText = '';
let replaceTextOnNextType = false; let replaceTextOnNextType = false;
let pendingEscapeDisconnect = false; let pendingEscapeDisconnect = false;
let micGainLoopbackRestoreState: boolean | null = null; let micGainLoopbackRestoreState: boolean | null = null;
let mainHelpViewerLines: string[] = [];
let pianoHelpViewerLines: string[] = [];
let helpViewerLines: string[] = []; let helpViewerLines: string[] = [];
let helpViewerIndex = 0; let helpViewerIndex = 0;
let helpViewerReturnMode: GameMode = 'normal';
let heartbeatTimerId: number | null = null; let heartbeatTimerId: number | null = null;
let heartbeatNextPingId = -1; let heartbeatNextPingId = -1;
let heartbeatAwaitingPong = false; let heartbeatAwaitingPong = false;
@@ -311,6 +327,7 @@ loadAudioLayerState();
loadMicInputGain(); loadMicInputGain();
loadMasterVolume(); loadMasterVolume();
void loadHelp(); void loadHelp();
void loadPianoHelp();
void loadChangelog(); void loadChangelog();
/** Fetches a required DOM element and casts it to the requested element type. */ /** Fetches a required DOM element and casts it to the requested element type. */
@@ -369,7 +386,7 @@ function setUpdatesExpanded(expanded: boolean): void {
/** Renders help sections into the footer help container and builds linearized viewer lines. */ /** Renders help sections into the footer help container and builds linearized viewer lines. */
function renderHelp(help: HelpData): void { function renderHelp(help: HelpData): void {
const lines: string[] = []; const lines = buildHelpLines(help);
dom.instructions.innerHTML = ''; dom.instructions.innerHTML = '';
const heading = document.createElement('h2'); const heading = document.createElement('h2');
heading.textContent = 'Help'; heading.textContent = 'Help';
@@ -386,9 +403,9 @@ 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}`);
} }
} }
mainHelpViewerLines = lines;
helpViewerLines = lines; helpViewerLines = lines;
helpViewerIndex = 0; helpViewerIndex = 0;
} }
@@ -410,6 +427,23 @@ async function loadHelp(): Promise<void> {
} }
} }
/** Loads piano-mode help content from `piano.json` for in-mode help viewing. */
async function loadPianoHelp(): Promise<void> {
try {
const response = await fetch(withBase('piano.json'), { cache: 'no-store' });
if (!response.ok) {
return;
}
const help = (await response.json()) as HelpData;
if (!Array.isArray(help.sections) || help.sections.length === 0) {
return;
}
pianoHelpViewerLines = buildHelpLines(help);
} catch {
// Keep piano help unavailable if loading fails.
}
}
/** Renders changelog sections into the collapsible updates panel. */ /** Renders changelog sections into the collapsible updates panel. */
function renderChangelog(changelog: ChangelogData): void { function renderChangelog(changelog: ChangelogData): void {
dom.updatesPanel.innerHTML = ''; dom.updatesPanel.innerHTML = '';
@@ -887,7 +921,7 @@ async function startPianoUseMode(itemId: string): Promise<void> {
activePianoMonophonicKey = null; activePianoMonophonicKey = null;
state.mode = 'pianoUse'; state.mode = 'pianoUse';
await audio.ensureContext(); await audio.ensureContext();
updateStatus(`using ${item.title}, press escape to stop.`); updateStatus(`using ${item.title}, press question mark for help.`);
audio.sfxUiBlip(); audio.sfxUiBlip();
} }
@@ -1112,12 +1146,14 @@ function stopRemotePianoNotesForSource(senderId: string, itemId: string): void {
} }
/** Enters help-view mode and announces the first help line. */ /** Enters help-view mode and announces the first help line. */
function openHelpViewer(): void { function openHelpViewer(lines: string[], returnMode: GameMode = 'normal'): void {
if (helpViewerLines.length === 0) { if (lines.length === 0) {
updateStatus('Help unavailable.'); updateStatus('Help unavailable.');
audio.sfxUiCancel(); audio.sfxUiCancel();
return; return;
} }
helpViewerLines = lines;
helpViewerReturnMode = returnMode;
state.mode = 'helpView'; state.mode = 'helpView';
helpViewerIndex = 0; helpViewerIndex = 0;
updateStatus(helpViewerLines[helpViewerIndex]); updateStatus(helpViewerLines[helpViewerIndex]);
@@ -2236,7 +2272,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
return; return;
} }
case 'openHelp': case 'openHelp':
openHelpViewer(); openHelpViewer(mainHelpViewerLines);
return; return;
case 'openChat': case 'openChat':
state.mode = 'chat'; state.mode = 'chat';
@@ -2305,7 +2341,7 @@ function handleHelpViewModeInput(code: string): void {
return; return;
} }
if (code === 'Escape') { if (code === 'Escape') {
state.mode = 'normal'; state.mode = helpViewerReturnMode;
updateStatus('Closed help.'); updateStatus('Closed help.');
audio.sfxUiCancel(); audio.sfxUiCancel();
} }
@@ -2406,6 +2442,10 @@ function handlePianoUseModeInput(code: string): void {
stopPianoUseMode(true); stopPianoUseMode(true);
return; return;
} }
if (code === 'Slash') {
openHelpViewer(pianoHelpViewerLines, 'pianoUse');
return;
}
const itemId = activePianoItemId; const itemId = activePianoItemId;
if (!itemId) { if (!itemId) {
state.mode = 'normal'; state.mode = 'normal';

View File

@@ -80,6 +80,7 @@ Applies to effect select, user/item list modes, item selection, item property li
- `A S D F G H J K L ; '`: Play white keys (C major from C4 upward) - `A S D F G H J K L ; '`: Play white keys (C major from C4 upward)
- `W E T Y U O P ]`: Play sharps - `W E T Y U O P ]`: Play sharps
- Multiple keys can be held/played at once - Multiple keys can be held/played at once
- `?`: Open piano-mode help viewer
- `-` / `=`: Shift octave down/up - `-` / `=`: Shift octave down/up
- `Z`: Start/stop recording on this piano (max 30s) - `Z`: Start/stop recording on this piano (max 30s)
- `X`: Play back saved recording on this piano - `X`: Play back saved recording on this piano