mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
559d860a7b
InfoStandWidgetUserView previously subscribed to three room-session events (RSUBE_BADGES, USER_FIGURE, FAVOURITE_GROUP_UPDATE) and pushed the result back to its parent via a setAvatarInfo prop, with each handler running CloneObject(prev) before patching one field. Three issues with that shape: - CloneObject was deep-cloning the whole AvatarInfoUser shape blindly with no class-prototype awareness; - the three listeners raced on shallow merges across the same prev reference in StrictMode dev; - the subscriptions lived outside the state owner, forcing a prop callback barrier per event. The subscriptions are now in useAvatarInfoWidget — the actual owner of avatarInfo — and call three pure reducers extracted to src/hooks/rooms/widgets/avatarInfo.reducers.ts (applyUserBadgesUpdate, applyUserFigureUpdate, applyFavouriteGroupUpdate). Each reducer returns the same reference when the event doesn't apply so React bail-outs work. The clone now constructs a fresh AvatarInfoUser preserving prototype. dedupeBadges is extracted to its own pure module under src/api/avatar/ so Vitest can cover it without pulling in the renderer. InfoStandWidgetUserView loses the setAvatarInfo prop (parent updated) and the CloneObject import.
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent } from '@nitrots/nitro-renderer';
|
|
import { AvatarInfoUser, dedupeBadges, IAvatarInfo } from '../../../api';
|
|
|
|
/**
|
|
* Pure reducers consumed by useAvatarInfoWidget to update the inspected
|
|
* AvatarInfoUser when room-session events fire. Exported standalone for
|
|
* Vitest coverage — no React, no renderer dispatcher access.
|
|
*
|
|
* Each reducer returns the same reference if the event doesn't apply
|
|
* (state unchanged) so React bail-outs work and consumers don't re-render
|
|
* uselessly.
|
|
*/
|
|
|
|
const cloneAvatarInfoUser = (state: AvatarInfoUser): AvatarInfoUser =>
|
|
{
|
|
const clone = new AvatarInfoUser(state.type);
|
|
|
|
Object.assign(clone, state);
|
|
|
|
return clone;
|
|
};
|
|
|
|
export const applyUserBadgesUpdate = (state: IAvatarInfo | null, event: RoomSessionUserBadgesEvent): IAvatarInfo | null =>
|
|
{
|
|
if(!(state instanceof AvatarInfoUser)) return state;
|
|
if(state.webID !== event.userId) return state;
|
|
|
|
const dedupedBadges = dedupeBadges(event.badges);
|
|
|
|
if(state.badges.join('') === dedupedBadges.join('')) return state;
|
|
|
|
const next = cloneAvatarInfoUser(state);
|
|
|
|
next.badges = dedupedBadges;
|
|
|
|
return next;
|
|
};
|
|
|
|
export const applyUserFigureUpdate = (state: IAvatarInfo | null, event: RoomSessionUserFigureUpdateEvent): IAvatarInfo | null =>
|
|
{
|
|
if(!(state instanceof AvatarInfoUser)) return state;
|
|
if(state.roomIndex !== event.roomIndex) return state;
|
|
|
|
const next = cloneAvatarInfoUser(state);
|
|
|
|
next.figure = event.figure;
|
|
next.motto = event.customInfo;
|
|
next.achievementScore = event.activityPoints;
|
|
next.nickIcon = event.nickIcon;
|
|
next.prefixText = event.prefixText;
|
|
next.prefixColor = event.prefixColor;
|
|
next.prefixIcon = event.prefixIcon;
|
|
next.prefixEffect = event.prefixEffect;
|
|
next.displayOrder = event.displayOrder;
|
|
next.backgroundId = event.backgroundId;
|
|
next.standId = event.standId;
|
|
next.overlayId = event.overlayId;
|
|
next.cardBackgroundId = event.cardBackgroundId ?? 0;
|
|
|
|
return next;
|
|
};
|
|
|
|
export const applyFavouriteGroupUpdate = (
|
|
state: IAvatarInfo | null,
|
|
event: RoomSessionFavoriteGroupUpdateEvent,
|
|
resolveGroupBadge: (groupId: number) => string
|
|
): IAvatarInfo | null =>
|
|
{
|
|
if(!(state instanceof AvatarInfoUser)) return state;
|
|
if(state.roomIndex !== event.roomIndex) return state;
|
|
|
|
const clearGroup = (event.status === -1) || (event.habboGroupId <= 0);
|
|
const next = cloneAvatarInfoUser(state);
|
|
|
|
next.groupId = clearGroup ? -1 : event.habboGroupId;
|
|
next.groupName = clearGroup ? null : event.habboGroupName;
|
|
next.groupBadgeId = clearGroup ? null : resolveGroupBadge(event.habboGroupId);
|
|
|
|
return next;
|
|
};
|