Include self in transfer targets and exclude item owner

This commit is contained in:
Jage9
2026-02-28 20:13:39 -05:00
parent c1cf100898
commit 83b7e1f9ce
7 changed files with 39 additions and 18 deletions

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.03.01 R320"; window.CHGRID_WEB_VERSION = "2026.03.01 R321";
// 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

@@ -1060,12 +1060,18 @@ function itemManagementOptionsFor(item: WorldItem): ItemManagementOption[] {
if (canManageDeleteItem(item)) { if (canManageDeleteItem(item)) {
options.push({ action: 'delete', label: 'Delete item' }); options.push({ action: 'delete', label: 'Delete item' });
} }
if (canManageTransferItem(item) && state.peers.size > 0) { if (canManageTransferItem(item) && (state.player.id !== null || state.peers.size > 0)) {
options.push({ action: 'transfer', label: 'Transfer item' }); options.push({ action: 'transfer', label: 'Transfer item' });
} }
return options; return options;
} }
/** Resolves transfer-target label for either local player id or a remote peer id. */
function transferTargetLabel(userPeerId: string): string {
if (userPeerId === state.player.id) return state.player.nickname || 'Unknown user';
return state.peers.get(userPeerId)?.nickname ?? 'Unknown user';
}
/** Opens item-management options for one selected item. */ /** Opens item-management options for one selected item. */
function beginItemManagement(item: WorldItem): void { function beginItemManagement(item: WorldItem): void {
const options = itemManagementOptionsFor(item); const options = itemManagementOptionsFor(item);
@@ -2894,12 +2900,21 @@ function handleItemManageOptionsModeInput(code: string, key: string): void {
}); });
return; return;
} }
const targetIds = Array.from(state.peers.values()) const ownerUserId = item.createdBy.trim();
.map((peer) => peer.id) const targetIds = [
.filter((peerId) => peerId !== state.player.id && state.peers.has(peerId)) ...(state.player.id ? [state.player.id] : []),
...Array.from(state.peers.values()).map((peer) => peer.id),
]
.filter((peerId, index, arr) => arr.indexOf(peerId) === index)
.filter((peerId) => {
if (!ownerUserId) return true;
if (peerId === state.player.id) return authUserId !== ownerUserId;
const peer = state.peers.get(peerId);
return (peer?.userId ?? '') !== ownerUserId;
})
.sort((a, b) => { .sort((a, b) => {
const left = state.peers.get(a)?.nickname ?? ''; const left = transferTargetLabel(a);
const right = state.peers.get(b)?.nickname ?? ''; const right = transferTargetLabel(b);
return left.localeCompare(right, undefined, { sensitivity: 'base' }); return left.localeCompare(right, undefined, { sensitivity: 'base' });
}); });
if (targetIds.length === 0) { if (targetIds.length === 0) {
@@ -2910,7 +2925,7 @@ function handleItemManageOptionsModeInput(code: string, key: string): void {
itemManagementTargetUserIds = targetIds; itemManagementTargetUserIds = targetIds;
itemManagementTargetUserIndex = 0; itemManagementTargetUserIndex = 0;
state.mode = 'itemManageTransferUser'; state.mode = 'itemManageTransferUser';
const firstLabel = state.peers.get(itemManagementTargetUserIds[0])?.nickname ?? 'Unknown user'; const firstLabel = transferTargetLabel(itemManagementTargetUserIds[0]);
updateStatus(firstLabel); updateStatus(firstLabel);
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
@@ -2930,11 +2945,11 @@ function handleItemManageTransferUserModeInput(code: string, key: string): void
return; return;
} }
const control = handleListControlKey(code, key, itemManagementTargetUserIds, itemManagementTargetUserIndex, (userId) => { const control = handleListControlKey(code, key, itemManagementTargetUserIds, itemManagementTargetUserIndex, (userId) => {
return state.peers.get(userId)?.nickname ?? 'Unknown user'; return transferTargetLabel(userId);
}); });
if (control.type === 'move') { if (control.type === 'move') {
itemManagementTargetUserIndex = control.index; itemManagementTargetUserIndex = control.index;
const label = state.peers.get(itemManagementTargetUserIds[itemManagementTargetUserIndex])?.nickname ?? 'Unknown user'; const label = transferTargetLabel(itemManagementTargetUserIds[itemManagementTargetUserIndex]);
updateStatus(label); updateStatus(label);
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
@@ -2947,7 +2962,7 @@ function handleItemManageTransferUserModeInput(code: string, key: string): void
audio.sfxUiCancel(); audio.sfxUiCancel();
return; return;
} }
const targetLabel = state.peers.get(targetId)?.nickname ?? 'Unknown user'; const targetLabel = transferTargetLabel(targetId);
openItemManagementConfirm({ openItemManagementConfirm({
itemId: item.id, itemId: item.id,
action: 'transfer', action: 'transfer',

View File

@@ -154,11 +154,12 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
if (!deps.isPeerNegotiationReady()) { if (!deps.isPeerNegotiationReady()) {
deps.enqueuePendingSignal(message); deps.enqueuePendingSignal(message);
if (!deps.state.peers.has(message.senderId)) { if (!deps.state.peers.has(message.senderId)) {
deps.state.peers.set(message.senderId, { deps.state.peers.set(message.senderId, {
id: message.senderId, id: message.senderId,
nickname: deps.sanitizeName(message.senderNickname || 'user...') || 'user...', userId: null,
x: Number.isFinite(message.x) ? message.x : 20, nickname: deps.sanitizeName(message.senderNickname || 'user...') || 'user...',
y: Number.isFinite(message.y) ? message.y : 20, x: Number.isFinite(message.x) ? message.x : 20,
y: Number.isFinite(message.y) ? message.y : 20,
}); });
} }
break; break;
@@ -167,6 +168,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
if (!deps.state.peers.has(peer.id)) { if (!deps.state.peers.has(peer.id)) {
deps.state.peers.set(peer.id, { deps.state.peers.set(peer.id, {
id: peer.id, id: peer.id,
userId: null,
nickname: deps.sanitizeName(peer.nickname) || 'user...', nickname: deps.sanitizeName(peer.nickname) || 'user...',
x: peer.x, x: peer.x,
y: peer.y, y: peer.y,

View File

@@ -24,6 +24,7 @@ export const welcomeMessageSchema = z.object({
id: z.string(), id: z.string(),
player: z.object({ player: z.object({
id: z.string(), id: z.string(),
userId: z.string().nullable().optional(),
nickname: z.string(), nickname: z.string(),
x: z.number().int(), x: z.number().int(),
y: z.number().int(), y: z.number().int(),
@@ -31,6 +32,7 @@ export const welcomeMessageSchema = z.object({
users: z.array( users: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
userId: z.string().nullable().optional(),
nickname: z.string(), nickname: z.string(),
x: z.number().int(), x: z.number().int(),
y: z.number().int(), y: z.number().int(),

View File

@@ -62,6 +62,7 @@ export type Player = {
export type PeerState = { export type PeerState = {
id: string; id: string;
userId?: string | null;
nickname: string; nickname: string;
x: number; x: number;
y: number; y: number;

View File

@@ -209,6 +209,7 @@ ClientPacket = (
class RemoteUser(BaseModel): class RemoteUser(BaseModel):
id: str id: str
userId: str | None = None
nickname: str nickname: str
x: int x: int
y: int y: int

View File

@@ -1389,14 +1389,14 @@ class SignalingServer:
"""Send initial world snapshot to a newly connected client.""" """Send initial world snapshot to a newly connected client."""
users = [ users = [
RemoteUser(id=other.id, nickname=other.nickname, x=other.x, y=other.y) RemoteUser(id=other.id, userId=other.user_id, nickname=other.nickname, x=other.x, y=other.y)
for ws, other in self.clients.items() for ws, other in self.clients.items()
if ws is not client.websocket if ws is not client.websocket
] ]
packet = WelcomePacket( packet = WelcomePacket(
type="welcome", type="welcome",
id=client.id, id=client.id,
player=RemoteUser(id=client.id, nickname=client.nickname, x=client.x, y=client.y), player=RemoteUser(id=client.id, userId=client.user_id, nickname=client.nickname, x=client.x, y=client.y),
users=users, users=users,
items=[self._outbound_item(item).model_dump(exclude_none=True) for item in self.items.values()], items=[self._outbound_item(item).model_dump(exclude_none=True) for item in self.items.values()],
worldConfig={ worldConfig={