Unify user list on Shift+L and remove Shift+U mode
This commit is contained in:
@@ -30,16 +30,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keys": "Shift+L",
|
"keys": "Shift+L",
|
||||||
"description": "List users; Enter teleports to that user"
|
"description": "List users; Enter teleports to that user, left/right arrow adjust volume"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keys": "U",
|
"keys": "U",
|
||||||
"description": "Speak connected users"
|
"description": "Speak connected users"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"keys": "Shift+U",
|
|
||||||
"description": "List users alphabetically; Left/Right adjusts selected user volume"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"keys": "Slash",
|
"keys": "Slash",
|
||||||
"description": "Start chat"
|
"description": "Start chat"
|
||||||
|
|||||||
@@ -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 R174";
|
window.CHGRID_WEB_VERSION = "2026.02.22 R175";
|
||||||
// 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";
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export type MainModeCommand =
|
|||||||
| 'calibrateMicrophone'
|
| 'calibrateMicrophone'
|
||||||
| 'useItem'
|
| 'useItem'
|
||||||
| 'speakUsers'
|
| 'speakUsers'
|
||||||
| 'listUsersAlphabetical'
|
|
||||||
| 'addItem'
|
| 'addItem'
|
||||||
| 'locateOrListItems'
|
| 'locateOrListItems'
|
||||||
| 'pickupDropOrDelete'
|
| 'pickupDropOrDelete'
|
||||||
@@ -53,7 +52,7 @@ export function resolveMainModeCommand(code: string, shiftKey: boolean): MainMod
|
|||||||
if (code === 'KeyC') return shiftKey ? null : 'speakCoordinates';
|
if (code === 'KeyC') return shiftKey ? null : 'speakCoordinates';
|
||||||
if (code === 'KeyV') return shiftKey ? 'calibrateMicrophone' : 'openMicGainEdit';
|
if (code === 'KeyV') return shiftKey ? 'calibrateMicrophone' : 'openMicGainEdit';
|
||||||
if (code === 'Enter') return 'useItem';
|
if (code === 'Enter') return 'useItem';
|
||||||
if (code === 'KeyU') return shiftKey ? 'listUsersAlphabetical' : 'speakUsers';
|
if (code === 'KeyU') return shiftKey ? null : 'speakUsers';
|
||||||
if (code === 'KeyA') return shiftKey ? null : 'addItem';
|
if (code === 'KeyA') return shiftKey ? null : 'addItem';
|
||||||
if (code === 'KeyI') return 'locateOrListItems';
|
if (code === 'KeyI') return 'locateOrListItems';
|
||||||
if (code === 'KeyD') return 'pickupDropOrDelete';
|
if (code === 'KeyD') return 'pickupDropOrDelete';
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ const RECONNECT_DELAY_MS = 5_000;
|
|||||||
const RECONNECT_MAX_ATTEMPTS = 3;
|
const RECONNECT_MAX_ATTEMPTS = 3;
|
||||||
const AUDIO_SUBSCRIPTION_REFRESH_MS = 500;
|
const AUDIO_SUBSCRIPTION_REFRESH_MS = 500;
|
||||||
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
|
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
|
||||||
const SELF_LIST_ENTRY_ID = '__self__';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -522,31 +521,6 @@ function applyConfiguredPeerListenGains(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolves a list user entry id to either local player or a remote peer. */
|
|
||||||
function getListUserEntry(peerId: string):
|
|
||||||
| { id: string; nickname: string; x: number; y: number; isSelf: true }
|
|
||||||
| { id: string; nickname: string; x: number; y: number; isSelf: false }
|
|
||||||
| null {
|
|
||||||
if (peerId === SELF_LIST_ENTRY_ID) {
|
|
||||||
return {
|
|
||||||
id: SELF_LIST_ENTRY_ID,
|
|
||||||
nickname: state.player.nickname,
|
|
||||||
x: state.player.x,
|
|
||||||
y: state.player.y,
|
|
||||||
isSelf: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const peer = state.peers.get(peerId);
|
|
||||||
if (!peer) return null;
|
|
||||||
return {
|
|
||||||
id: peer.id,
|
|
||||||
nickname: peer.nickname,
|
|
||||||
x: peer.x,
|
|
||||||
y: peer.y,
|
|
||||||
isSelf: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Applies current layer toggles to peer voice, media streams, and item emitters. */
|
/** Applies current layer toggles to peer voice, media streams, and item emitters. */
|
||||||
async function applyAudioLayerState(): Promise<void> {
|
async function applyAudioLayerState(): Promise<void> {
|
||||||
audio.setVoiceLayerEnabled(audioLayers.voice);
|
audio.setVoiceLayerEnabled(audioLayers.voice);
|
||||||
@@ -1534,31 +1508,6 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
|
|||||||
audio.sfxUiBlip();
|
audio.sfxUiBlip();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'listUsersAlphabetical':
|
|
||||||
state.sortedPeerIds = [
|
|
||||||
...Array.from(state.peers.entries()).map(([id, peer]) => ({ id, nickname: peer.nickname })),
|
|
||||||
{ id: SELF_LIST_ENTRY_ID, nickname: state.player.nickname },
|
|
||||||
]
|
|
||||||
.sort((a, b) => a.nickname.localeCompare(b.nickname, undefined, { sensitivity: 'base' }))
|
|
||||||
.map((entry) => entry.id);
|
|
||||||
state.listIndex = 0;
|
|
||||||
state.mode = 'listUsers';
|
|
||||||
{
|
|
||||||
const first = getListUserEntry(state.sortedPeerIds[0]);
|
|
||||||
if (first) {
|
|
||||||
const userCount = state.sortedPeerIds.length;
|
|
||||||
const userLabelText = userCount === 1 ? 'user' : 'users';
|
|
||||||
const gain = first.isSelf ? 1 : getPeerListenGainForNickname(first.nickname);
|
|
||||||
const gainPhrase = first.isSelf
|
|
||||||
? 'self volume not adjustable'
|
|
||||||
: `volume ${formatSteppedNumber(gain, MIC_INPUT_GAIN_STEP)}`;
|
|
||||||
updateStatus(
|
|
||||||
`${userCount} ${userLabelText}. ${first.nickname}, ${gainPhrase}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
audio.sfxUiBlip();
|
|
||||||
return;
|
|
||||||
case 'addItem': {
|
case 'addItem': {
|
||||||
const itemTypeSequence = getItemTypeSequence();
|
const itemTypeSequence = getItemTypeSequence();
|
||||||
if (itemTypeSequence.length === 0) {
|
if (itemTypeSequence.length === 0) {
|
||||||
@@ -1700,11 +1649,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.sortedPeerIds = Array.from(state.peers.entries())
|
state.sortedPeerIds = Array.from(state.peers.entries())
|
||||||
.sort(
|
.sort((a, b) => a[1].nickname.localeCompare(b[1].nickname, undefined, { sensitivity: 'base' }))
|
||||||
(a, b) =>
|
|
||||||
Math.hypot(a[1].x - state.player.x, a[1].y - state.player.y) -
|
|
||||||
Math.hypot(b[1].x - state.player.x, b[1].y - state.player.y),
|
|
||||||
)
|
|
||||||
.map(([id]) => id);
|
.map(([id]) => id);
|
||||||
state.listIndex = 0;
|
state.listIndex = 0;
|
||||||
state.mode = 'listUsers';
|
state.mode = 'listUsers';
|
||||||
@@ -1712,8 +1657,9 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
|
|||||||
if (first) {
|
if (first) {
|
||||||
const userCount = state.sortedPeerIds.length;
|
const userCount = state.sortedPeerIds.length;
|
||||||
const userLabelText = userCount === 1 ? 'user' : 'users';
|
const userLabelText = userCount === 1 ? 'user' : 'users';
|
||||||
|
const gainPhrase = `volume ${formatSteppedNumber(getPeerListenGainForNickname(first.nickname), MIC_INPUT_GAIN_STEP)}`;
|
||||||
updateStatus(
|
updateStatus(
|
||||||
`${userCount} ${userLabelText}. ${first.nickname}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`,
|
`${userCount} ${userLabelText}. ${first.nickname}, ${gainPhrase}, ${distanceDirectionPhrase(state.player.x, state.player.y, first.x, first.y)}, ${first.x}, ${first.y}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
audio.sfxUiBlip();
|
audio.sfxUiBlip();
|
||||||
@@ -1933,19 +1879,15 @@ function handleListModeInput(code: string, key: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code === 'ArrowLeft' || code === 'ArrowRight') {
|
if (code === 'ArrowLeft' || code === 'ArrowRight') {
|
||||||
const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
|
const peerId = state.sortedPeerIds[state.listIndex];
|
||||||
|
const entry = state.peers.get(peerId);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
if (entry.isSelf) {
|
|
||||||
updateStatus('Your own listen volume is not adjustable.');
|
|
||||||
audio.sfxUiCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const current = getPeerListenGainForNickname(entry.nickname);
|
const current = getPeerListenGainForNickname(entry.nickname);
|
||||||
const delta = code === 'ArrowRight' ? MIC_INPUT_GAIN_STEP : -MIC_INPUT_GAIN_STEP;
|
const delta = code === 'ArrowRight' ? MIC_INPUT_GAIN_STEP : -MIC_INPUT_GAIN_STEP;
|
||||||
const attempted = snapNumberToStep(current + delta, MIC_INPUT_GAIN_STEP, MIC_CALIBRATION_MIN_GAIN);
|
const attempted = snapNumberToStep(current + delta, MIC_INPUT_GAIN_STEP, MIC_CALIBRATION_MIN_GAIN);
|
||||||
const next = clampMicInputGain(attempted);
|
const next = clampMicInputGain(attempted);
|
||||||
setPeerListenGainForNickname(entry.nickname, next);
|
setPeerListenGainForNickname(entry.nickname, next);
|
||||||
peerManager.setPeerListenGain(entry.id, next);
|
peerManager.setPeerListenGain(peerId, next);
|
||||||
updateStatus(`${entry.nickname} volume ${formatSteppedNumber(next, MIC_INPUT_GAIN_STEP)}.`);
|
updateStatus(`${entry.nickname} volume ${formatSteppedNumber(next, MIC_INPUT_GAIN_STEP)}.`);
|
||||||
if (Math.abs(next - current) < 1e-9 || Math.abs(next - attempted) > 1e-9) {
|
if (Math.abs(next - current) < 1e-9 || Math.abs(next - attempted) > 1e-9) {
|
||||||
audio.sfxUiCancel();
|
audio.sfxUiCancel();
|
||||||
@@ -1955,17 +1897,18 @@ function handleListModeInput(code: string, key: string): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const control = handleListControlKey(code, key, state.sortedPeerIds, state.listIndex, (peerId) => {
|
const control = handleListControlKey(
|
||||||
const entry = getListUserEntry(peerId);
|
code,
|
||||||
return entry?.nickname ?? '';
|
key,
|
||||||
});
|
state.sortedPeerIds,
|
||||||
|
state.listIndex,
|
||||||
|
(peerId) => state.peers.get(peerId)?.nickname ?? '',
|
||||||
|
);
|
||||||
if (control.type === 'move') {
|
if (control.type === 'move') {
|
||||||
state.listIndex = control.index;
|
state.listIndex = control.index;
|
||||||
const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
|
const entry = state.peers.get(state.sortedPeerIds[state.listIndex]);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
const gainPhrase = entry.isSelf
|
const gainPhrase = `volume ${formatSteppedNumber(getPeerListenGainForNickname(entry.nickname), MIC_INPUT_GAIN_STEP)}`;
|
||||||
? 'self volume not adjustable'
|
|
||||||
: `volume ${formatSteppedNumber(getPeerListenGainForNickname(entry.nickname), MIC_INPUT_GAIN_STEP)}`;
|
|
||||||
updateStatus(
|
updateStatus(
|
||||||
`${entry.nickname}, ${gainPhrase}, ${distanceDirectionPhrase(state.player.x, state.player.y, entry.x, entry.y)}, ${entry.x}, ${entry.y}`,
|
`${entry.nickname}, ${gainPhrase}, ${distanceDirectionPhrase(state.player.x, state.player.y, entry.x, entry.y)}, ${entry.x}, ${entry.y}`,
|
||||||
);
|
);
|
||||||
@@ -1976,7 +1919,7 @@ function handleListModeInput(code: string, key: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (control.type === 'select') {
|
if (control.type === 'select') {
|
||||||
const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
|
const entry = state.peers.get(state.sortedPeerIds[state.listIndex]);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
if (state.player.x === entry.x && state.player.y === entry.y) {
|
if (state.player.x === entry.x && state.player.y === entry.y) {
|
||||||
updateStatus('Already here.');
|
updateStatus('Already here.');
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ This document is the authoritative keymap for the client.
|
|||||||
|
|
||||||
### Users, Nickname, Chat
|
### Users, Nickname, Chat
|
||||||
- `L`: Locate nearest user
|
- `L`: Locate nearest user
|
||||||
- `Shift+L`: List users and teleport to selected user with `Enter`
|
- `Shift+L`: List users alphabetically; `Enter` teleports to selected user; `ArrowLeft`/`ArrowRight` adjust selected user volume
|
||||||
- `U`: Speak connected users
|
- `U`: Speak connected users
|
||||||
- `Shift+U`: List users alphabetically
|
|
||||||
- `N`: Edit nickname
|
- `N`: Edit nickname
|
||||||
- `/`: Start chat
|
- `/`: Start chat
|
||||||
- `,` / `.`: Previous/next message
|
- `,` / `.`: Previous/next message
|
||||||
|
|||||||
Reference in New Issue
Block a user