Add piano-mode help viewer bound to question mark
This commit is contained in:
@@ -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
41
client/public/piano.json
Normal 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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user