Include self in transfer targets and exclude item owner
This commit is contained in:
@@ -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";
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
Reference in New Issue
Block a user