mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
Dynamic badge slots from config, double-click remove, direct wear from toast
Read user.badges.max.slots from config instead of hardcoded 5. InfoStand layout adapts: 5 slots shows group badge, 6 slots replaces group with 6th badge. Double-click on InfoStand badge removes it. Badge received toast now directly equips the badge via toggleBadge and closes.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { DeleteBadgeMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FaTrashAlt } from 'react-icons/fa';
|
||||
import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, SendMessageComposer, UnseenItemCategory } from '../../../../api';
|
||||
import { LayoutBadgeImageView } from '../../../../common';
|
||||
@@ -89,7 +89,7 @@ export const InventoryBadgeView: FC<{ filteredBadgeCodes?: string[] }> = props =
|
||||
const [ isDragOverInventory, setIsDragOverInventory ] = useState(false);
|
||||
const [ isDraggingFromActive, setIsDraggingFromActive ] = useState(false);
|
||||
|
||||
const maxSlots = 5;
|
||||
const maxSlots = useMemo(() => GetConfigurationValue<number>('user.badges.max.slots', 5), []);
|
||||
const displayCodes = (filteredBadgeCodes !== null ? filteredBadgeCodes : badgeCodes);
|
||||
|
||||
const attemptDeleteBadge = () =>
|
||||
|
||||
+11
-3
@@ -1,6 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText, NotificationBubbleItem, OpenUrl } from '../../../../api';
|
||||
import { LocalizeText, NotificationBubbleItem } from '../../../../api';
|
||||
import { Flex, LayoutNotificationBubbleView, LayoutNotificationBubbleViewProps, Text } from '../../../../common';
|
||||
import { useInventoryBadges } from '../../../../hooks';
|
||||
|
||||
export interface NotificationBadgeReceivedBubbleViewProps extends LayoutNotificationBubbleViewProps
|
||||
{
|
||||
@@ -10,9 +11,16 @@ export interface NotificationBadgeReceivedBubbleViewProps extends LayoutNotifica
|
||||
export const NotificationBadgeReceivedBubbleView: FC<NotificationBadgeReceivedBubbleViewProps> = props =>
|
||||
{
|
||||
const { item = null, onClose = null, ...rest } = props;
|
||||
const { toggleBadge = null } = useInventoryBadges();
|
||||
|
||||
const handleWear = () =>
|
||||
{
|
||||
if(item.linkUrl) toggleBadge(item.linkUrl);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<LayoutNotificationBubbleView className="flex-col" fadesOut={ false } onClose={ onClose } { ...rest }>
|
||||
<LayoutNotificationBubbleView className="flex-col" onClose={ onClose } { ...rest }>
|
||||
<Flex alignItems="center" gap={ 2 } className="mb-2">
|
||||
<Flex center className="w-[50px] h-[50px] shrink-0">
|
||||
{ item.iconUrl && <img alt="" className="no-select" src={ item.iconUrl } /> }
|
||||
@@ -26,7 +34,7 @@ export const NotificationBadgeReceivedBubbleView: FC<NotificationBadgeReceivedBu
|
||||
<button
|
||||
className="btn btn-success w-full btn-sm"
|
||||
type="button"
|
||||
onClick={ () => { OpenUrl(item.linkUrl); onClose(); } }>
|
||||
onClick={ handleWear }>
|
||||
{ LocalizeText('inventory.badges.wearbadge') }
|
||||
</button>
|
||||
<span className="underline cursor-pointer text-nowrap" onClick={ onClose }>
|
||||
|
||||
@@ -79,7 +79,7 @@ const BadgeMiniPicker: FC<{
|
||||
|
||||
export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex, badgeCode: badgeCodeFromProps, isOwnUser }) =>
|
||||
{
|
||||
const { activeBadgeCodes = [], setBadgeAtSlot = null, swapBadges = null, requestBadges = null } = useInventoryBadges();
|
||||
const { activeBadgeCodes = [], setBadgeAtSlot = null, swapBadges = null, removeBadge = null, requestBadges = null } = useInventoryBadges();
|
||||
const [ isDragOver, setIsDragOver ] = useState(false);
|
||||
const [ isDragging, setIsDragging ] = useState(false);
|
||||
const [ justDropped, setJustDropped ] = useState(false);
|
||||
@@ -157,6 +157,13 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
setShowPicker(true);
|
||||
}, [ isOwnUser, badgeCode ]);
|
||||
|
||||
const handleDoubleClick = useCallback(() =>
|
||||
{
|
||||
if(!isOwnUser || !badgeCode) return;
|
||||
|
||||
removeBadge(badgeCode);
|
||||
}, [ isOwnUser, badgeCode, removeBadge ]);
|
||||
|
||||
const handlePickerSelect = useCallback((code: string) =>
|
||||
{
|
||||
setBadgeAtSlot(code, slotIndex);
|
||||
@@ -180,7 +187,8 @@ export const InfoStandBadgeSlotView: FC<InfoStandBadgeSlotProps> = ({ slotIndex,
|
||||
onDragOver={ onDragOver }
|
||||
onDragStart={ onDragStart }
|
||||
onDrop={ onDrop }
|
||||
onClick={ handleSlotClick }>
|
||||
onClick={ handleSlotClick }
|
||||
onDoubleClick={ handleDoubleClick }>
|
||||
{ badgeCode
|
||||
? <LayoutBadgeImageView badgeCode={ badgeCode } showInfo={ true } />
|
||||
: isOwnUser && <FaPlus className="text-white/30 text-[10px]" /> }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FaPencilAlt, FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoUser, CloneObject, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserProfileIconView } from '../../../../../common';
|
||||
@@ -173,43 +174,38 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
||||
/>
|
||||
)}
|
||||
<Column grow alignItems="center" gap={0}>
|
||||
{ GetConfigurationValue<boolean>('user.badges.group.slot.enabled', true)
|
||||
? (
|
||||
<>
|
||||
<div className="flex gap-1">
|
||||
<InfoStandBadgeSlotView slotIndex={0} badgeCode={avatarInfo.badges[0]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<Flex center className="relative w-[40px] h-[40px] bg-no-repeat bg-center" pointer={avatarInfo.groupId > 0} onClick={event => GetGroupInformation(avatarInfo.groupId)}>
|
||||
{avatarInfo.groupId > 0 &&
|
||||
<LayoutBadgeImageView badgeCode={avatarInfo.groupBadgeId} customTitle={avatarInfo.groupName} isGroup={true} showInfo={true} />}
|
||||
</Flex>
|
||||
</div>
|
||||
<Flex center gap={1}>
|
||||
<InfoStandBadgeSlotView slotIndex={1} badgeCode={avatarInfo.badges[1]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<InfoStandBadgeSlotView slotIndex={2} badgeCode={avatarInfo.badges[2]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
{ (() => {
|
||||
const maxSlots = GetConfigurationValue<number>('user.badges.max.slots', 5);
|
||||
const isOwnUser = avatarInfo.type === AvatarInfoUser.OWN_USER;
|
||||
const showGroup = maxSlots <= 5;
|
||||
|
||||
const items: React.ReactNode[] = [];
|
||||
items.push(<InfoStandBadgeSlotView key={0} slotIndex={0} badgeCode={avatarInfo.badges[0]} isOwnUser={isOwnUser} />);
|
||||
|
||||
if(showGroup) {
|
||||
items.push(
|
||||
<Flex key="group" center className="relative w-[40px] h-[40px] bg-no-repeat bg-center" pointer={avatarInfo.groupId > 0} onClick={event => GetGroupInformation(avatarInfo.groupId)}>
|
||||
{avatarInfo.groupId > 0 && <LayoutBadgeImageView badgeCode={avatarInfo.groupBadgeId} customTitle={avatarInfo.groupName} isGroup={true} showInfo={true} />}
|
||||
</Flex>
|
||||
<Flex center gap={1}>
|
||||
<InfoStandBadgeSlotView slotIndex={3} badgeCode={avatarInfo.badges[3]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<InfoStandBadgeSlotView slotIndex={4} badgeCode={avatarInfo.badges[4]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Flex center gap={1}>
|
||||
<InfoStandBadgeSlotView slotIndex={0} badgeCode={avatarInfo.badges[0]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<InfoStandBadgeSlotView slotIndex={1} badgeCode={avatarInfo.badges[1]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
</Flex>
|
||||
<Flex center gap={1}>
|
||||
<InfoStandBadgeSlotView slotIndex={2} badgeCode={avatarInfo.badges[2]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<InfoStandBadgeSlotView slotIndex={3} badgeCode={avatarInfo.badges[3]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
</Flex>
|
||||
<Flex center gap={1}>
|
||||
<InfoStandBadgeSlotView slotIndex={4} badgeCode={avatarInfo.badges[4]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
<InfoStandBadgeSlotView slotIndex={5} badgeCode={avatarInfo.badges[5]} isOwnUser={avatarInfo.type === AvatarInfoUser.OWN_USER} />
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
} else {
|
||||
items.push(<InfoStandBadgeSlotView key="slot1" slotIndex={1} badgeCode={avatarInfo.badges[1]} isOwnUser={isOwnUser} />);
|
||||
}
|
||||
|
||||
const startIdx = showGroup ? 1 : 2;
|
||||
for(let i = startIdx; i < maxSlots; i++) {
|
||||
items.push(<InfoStandBadgeSlotView key={i} slotIndex={i} badgeCode={avatarInfo.badges[i]} isOwnUser={isOwnUser} />);
|
||||
}
|
||||
|
||||
const rows: React.ReactNode[][] = [];
|
||||
for(let i = 0; i < items.length; i += 2) {
|
||||
rows.push(items.slice(i, i + 2));
|
||||
}
|
||||
|
||||
return rows.map((row, idx) => (
|
||||
<Flex key={idx} center gap={1}>{row}</Flex>
|
||||
));
|
||||
})() }
|
||||
</Column>
|
||||
</div>
|
||||
<hr className="m-0 bg-[#0003] border-0 opacity-[0.5] h-px" />
|
||||
|
||||
@@ -223,7 +223,7 @@ const useNotificationState = () =>
|
||||
const badgeName = LocalizeBadgeName(parser.badgeCode);
|
||||
const badgeImage = GetSessionDataManager().getBadgeUrl(parser.badgeCode);
|
||||
|
||||
showSingleBubble(badgeName, NotificationBubbleType.BADGE_RECEIVED, badgeImage, 'inventory/toggle');
|
||||
showSingleBubble(badgeName, NotificationBubbleType.BADGE_RECEIVED, badgeImage, parser.badgeCode);
|
||||
});
|
||||
|
||||
useMessageEvent<ClubGiftNotificationEvent>(ClubGiftNotificationEvent, event =>
|
||||
|
||||
Reference in New Issue
Block a user