This commit is contained in:
Bill 2022-04-03 14:50:31 -04:00
parent ced2e2540e
commit 77cb36be44
38 changed files with 341 additions and 535 deletions

View File

@ -6,7 +6,7 @@ export const GetAchievementCategoryTotalUnseen = (category: IAchievementCategory
let unseen = 0; let unseen = 0;
for(const achievement of category.achievements) unseen += achievement.unseen; for(const achievement of category.achievements) ((achievement.unseen > 0) && unseen++);
return unseen; return unseen;
} }

View File

@ -1 +0,0 @@
export * from './ProductImageUtility';

View File

@ -1,13 +1,13 @@
export * from './achievements'; export * from './achievements';
export * from './campaign'; export * from './campaign';
export * from './catalog'; export * from './catalog';
export * from './common';
export * from './core'; export * from './core';
export * from './friends'; export * from './friends';
export * from './GetRendererVersion'; export * from './GetRendererVersion';
export * from './GetUIVersion'; export * from './GetUIVersion';
export * from './groups'; export * from './groups';
export * from './inventory'; export * from './inventory';
export * from './inventory/unseen';
export * from './navigator'; export * from './navigator';
export * from './nitro'; export * from './nitro';
export * from './nitro/avatar'; export * from './nitro/avatar';

View File

@ -1,8 +1,8 @@
import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespectComposer, PetSupplementComposer, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectOperationType, RoomObjectType, RoomObjectVariable, RoomSessionFavoriteGroupUpdateEvent, RoomSessionPetInfoUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, RoomTradingLevelEnum, RoomUnitDropHandItemComposer, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUserData, RoomWidgetEnum, RoomWidgetEnumItemExtradataParameter, Vector3d } from '@nitrots/nitro-renderer'; import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespectComposer, PetSupplementComposer, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectOperationType, RoomObjectType, RoomObjectVariable, RoomSessionFavoriteGroupUpdateEvent, RoomSessionPetInfoUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, RoomTradingLevelEnum, RoomUnitDropHandItemComposer, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUserData, RoomWidgetEnum, RoomWidgetEnumItemExtradataParameter, TradingOpenComposer, Vector3d } from '@nitrots/nitro-renderer';
import { SendMessageComposer } from '../../..'; import { SendMessageComposer } from '../../..';
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..'; import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
import { PetSupplementEnum } from '../../../../../components/room/widgets/avatar-info/common/PetSupplementEnum'; import { PetSupplementEnum } from '../../../../../components/room/widgets/avatar-info/common/PetSupplementEnum';
import { HelpReportUserEvent, InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events'; import { HelpReportUserEvent, WiredSelectObjectEvent } from '../../../../../events';
import { DispatchUiEvent } from '../../../../../hooks'; import { DispatchUiEvent } from '../../../../../hooks';
import { LocalizeText } from '../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../utils/LocalizeText';
import { RoomWidgetObjectNameEvent, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent } from '../events'; import { RoomWidgetObjectNameEvent, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent } from '../events';
@ -119,7 +119,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
this.container.roomSession.sendTakeRightsMessage((message as RoomWidgetUserActionMessage).userId); this.container.roomSession.sendTakeRightsMessage((message as RoomWidgetUserActionMessage).userId);
break; break;
case RoomWidgetUserActionMessage.START_TRADING: case RoomWidgetUserActionMessage.START_TRADING:
DispatchUiEvent(new InventoryTradeRequestEvent(userData.roomIndex, userData.name)); SendMessageComposer(new TradingOpenComposer(userData.roomIndex));
break; break;
// case RoomWidgetUserActionMessage.RWUAM_OPEN_HOME_PAGE: // case RoomWidgetUserActionMessage.RWUAM_OPEN_HOME_PAGE:
// this._container.sessionDataManager._Str_21275((message as RoomWidgetUserActionMessage).userId, _local_3.name); // this._container.sessionDataManager._Str_21275((message as RoomWidgetUserActionMessage).userId, _local_3.name);

View File

@ -1,6 +1,6 @@
import { CatalogPageMessageProductData } from '@nitrots/nitro-renderer'; import { CatalogPageMessageProductData } from '@nitrots/nitro-renderer';
import { GetRoomEngine } from '..'; import { FurniCategory } from '../inventory';
import { FurniCategory } from '../../components/catalog/common/FurniCategory'; import { GetRoomEngine } from '../nitro';
export class ProductImageUtility export class ProductImageUtility
{ {

View File

@ -6,6 +6,7 @@ export * from './LocalizeFormattedNumber';
export * from './LocalizeShortNumber'; export * from './LocalizeShortNumber';
export * from './LocalizeText'; export * from './LocalizeText';
export * from './PlaySound'; export * from './PlaySound';
export * from './ProductImageUtility';
export * from './Randomizer'; export * from './Randomizer';
export * from './RoomChatFormatter'; export * from './RoomChatFormatter';
export * from './SoundNames'; export * from './SoundNames';

View File

@ -1,236 +1,69 @@
import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, ILinkEventTracker, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer'; import { ILinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { AchievementCategory, AddEventLinkTracker, CloneObject, GetAchievementCategoryImageUrl, GetAchievementIsIgnored, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../api'; import { AddEventLinkTracker, GetAchievementCategoryImageUrl, LocalizeText, RemoveLinkEventTracker } from '../../api';
import { Base, Column, LayoutImage, LayoutProgressBar, NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, Text } from '../../common'; import { Base, Column, LayoutImage, LayoutProgressBar, NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, Text } from '../../common';
import { AchievementsUIUnseenCountEvent } from '../../events'; import { useAchievements } from '../../hooks';
import { DispatchUiEvent, UseMessageEventHook } from '../../hooks';
import { AchievementCategoryView } from './views/AchievementCategoryView'; import { AchievementCategoryView } from './views/AchievementCategoryView';
import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView'; import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView';
export const AchievementsView: FC<{}> = props => export const AchievementsView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const [ isInitalized, setIsInitalized ] = useState(false); const { achievementCategories = [], selectedCategoryCode = null, setSelectedCategoryCode = null, selectedAchievementId = -1, setSelectedAchievementId = null, achievementScore = 0, getProgress = 0, getMaxProgress = 0, setAchievementSeen = null } = useAchievements();
const [ achievementCategories, setAchievementCategories ] = useState<AchievementCategory[]>([]);
const [ selectedCategoryCode, setSelectedCategoryCode ] = useState<string>(null);
const [ achievementScore, setAchievementScore ] = useState(0);
const onAchievementEvent = useCallback((event: AchievementEvent) => const selectedCategory = useMemo(() =>
{ {
const parser = event.getParser(); if(selectedCategoryCode === null) return null;
const achievement = parser.achievement;
const categoryName = achievement.category;
setAchievementCategories(prevValue => return achievementCategories.find(category => (category.code === selectedCategoryCode));
{
const newValue = [ ...prevValue ];
const categoryIndex = newValue.findIndex(existing => (existing.code === categoryName));
if(categoryIndex === -1)
{
const category = new AchievementCategory(categoryName);
category.achievements.push(achievement);
newValue.push(category);
}
else
{
const category = CloneObject(newValue[categoryIndex]);
const newAchievements = [ ...category.achievements ];
const achievementIndex = newAchievements.findIndex(existing => (existing.achievementId === achievement.achievementId));
let previousAchievement: AchievementData = null;
if(achievementIndex === -1)
{
newAchievements.push(achievement);
}
else
{
previousAchievement = newAchievements[achievementIndex];
newAchievements[achievementIndex] = achievement;
}
if(!GetAchievementIsIgnored(achievement))
{
achievement.unseen++;
if(previousAchievement) achievement.unseen += previousAchievement.unseen;
}
category.achievements = newAchievements;
newValue[categoryIndex] = category;
}
return newValue;
});
}, []);
UseMessageEventHook(AchievementEvent, onAchievementEvent);
const onAchievementsEvent = useCallback((event: AchievementsEvent) =>
{
const parser = event.getParser();
const categories: AchievementCategory[] = [];
for(const achievement of parser.achievements)
{
const categoryName = achievement.category;
let existing = categories.find(category => (category.code === categoryName));
if(!existing)
{
existing = new AchievementCategory(categoryName);
categories.push(existing);
}
existing.achievements.push(achievement);
}
setAchievementCategories(categories);
setIsInitalized(true);
}, []);
UseMessageEventHook(AchievementsEvent, onAchievementsEvent);
const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) =>
{
const parser = event.getParser();
setAchievementScore(parser.score);
}, []);
UseMessageEventHook(AchievementsScoreEvent, onAchievementsScoreEvent);
const getTotalUnseen = useMemo(() =>
{
let unseen = 0;
for(const category of achievementCategories)
{
for(const achievement of category.achievements) unseen += achievement.unseen;
}
return unseen;
}, [ achievementCategories ]);
const getProgress = useMemo(() =>
{
let progress = 0;
for(const category of achievementCategories) progress += category.getProgress();
return progress;
}, [ achievementCategories ]);
const getMaxProgress = useMemo(() =>
{
let progress = 0;
for(const category of achievementCategories) progress += category.getMaxProgress();
return progress;
}, [ achievementCategories ]);
const scaledProgressPercent = useMemo(() =>
{
return ~~((((getProgress - 0) * (100 - 0)) / (getMaxProgress - 0)) + 0);
}, [ getProgress, getMaxProgress ]);
const getSelectedCategory = useMemo(() =>
{
if(!achievementCategories || !achievementCategories.length) return null;
return achievementCategories.find(existing => (existing.code === selectedCategoryCode));
}, [ achievementCategories, selectedCategoryCode ]); }, [ achievementCategories, selectedCategoryCode ]);
const setAchievementSeen = useCallback((code: string, achievementId: number) =>
{
setAchievementCategories(prevValue =>
{
const newValue = [ ...prevValue ];
for(const category of newValue)
{
if(category.code !== code) continue;
for(const achievement of category.achievements)
{
if(achievement.achievementId !== achievementId) continue;
achievement.unseen = 0;
}
}
return newValue;
});
}, []);
const linkReceived = useCallback((url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'show':
setIsVisible(true);
return;
case 'hide':
setIsVisible(false);
return;
case 'toggle':
setIsVisible(prevValue => !prevValue);
return;
}
}, []);
useEffect(() => useEffect(() =>
{ {
const linkTracker: ILinkEventTracker = { const linkTracker: ILinkEventTracker = {
linkReceived, linkReceived: (url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'show':
setIsVisible(true);
return;
case 'hide':
setIsVisible(false);
return;
case 'toggle':
setIsVisible(prevValue => !prevValue);
return;
}
},
eventUrlPrefix: 'achievements/' eventUrlPrefix: 'achievements/'
}; };
AddEventLinkTracker(linkTracker); AddEventLinkTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker); return () => RemoveLinkEventTracker(linkTracker);
}, [ linkReceived ]); }, []);
useEffect(() => if(!isVisible) return null;
{
if(!isVisible || isInitalized) return;
SendMessageComposer(new RequestAchievementsMessageComposer());
}, [ isVisible, isInitalized ]);
useEffect(() =>
{
DispatchUiEvent(new AchievementsUIUnseenCountEvent(getTotalUnseen));
}, [ getTotalUnseen ]);
if(!isVisible || !isInitalized) return null;
return ( return (
<NitroCardView uniqueKey="achievements" className="nitro-achievements" theme="primary-slim"> <NitroCardView uniqueKey="achievements" className="nitro-achievements" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
{ getSelectedCategory && { selectedCategory &&
<NitroCardSubHeaderView position="relative" className="justify-content-center align-items-center cursor-pointer" gap={ 3 }> <NitroCardSubHeaderView position="relative" className="justify-content-center align-items-center cursor-pointer" gap={ 3 }>
<Base onClick={ event => setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" /> <Base onClick={ event => setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" />
<Column grow gap={ 0 }> <Column grow gap={ 0 }>
<Text fontSize={ 4 } fontWeight="bold" className="text-small">{ LocalizeText(`quests.${ getSelectedCategory.code }.name`) }</Text> <Text fontSize={ 4 } fontWeight="bold" className="text-small">{ LocalizeText(`quests.${ selectedCategory.code }.name`) }</Text>
<Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) }</Text> <Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString() ]) }</Text>
</Column> </Column>
<LayoutImage imageUrl={ GetAchievementCategoryImageUrl(getSelectedCategory, null,true) } /> <LayoutImage imageUrl={ GetAchievementCategoryImageUrl(selectedCategory, null,true) } />
</NitroCardSubHeaderView> } </NitroCardSubHeaderView> }
<NitroCardContentView gap={ 1 }> <NitroCardContentView gap={ 1 }>
{ !getSelectedCategory && { !selectedCategory &&
<> <>
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } /> <AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
<Column grow justifyContent="end" gap={ 1 }> <Column grow justifyContent="end" gap={ 1 }>
@ -238,8 +71,8 @@ export const AchievementsView: FC<{}> = props =>
<LayoutProgressBar text={ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } progress={ getProgress } maxProgress={ getMaxProgress } /> <LayoutProgressBar text={ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } progress={ getProgress } maxProgress={ getMaxProgress } />
</Column> </Column>
</> } </> }
{ getSelectedCategory && { selectedCategory &&
<AchievementCategoryView category={ getSelectedCategory } setAchievementSeen={ setAchievementSeen } /> } <AchievementCategoryView category={ selectedCategory } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } setAchievementSeen={ setAchievementSeen } /> }
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>
); );

View File

@ -1,4 +1,4 @@
import { FC, useEffect, useMemo, useState } from 'react'; import { Dispatch, FC, SetStateAction, useEffect, useMemo } from 'react';
import { AchievementCategory } from '../../../api'; import { AchievementCategory } from '../../../api';
import { Column } from '../../../common'; import { Column } from '../../../common';
import { AchievementListView } from './achievement-list/AchievementListView'; import { AchievementListView } from './achievement-list/AchievementListView';
@ -7,40 +7,44 @@ import { AchievementDetailsView } from './AchievementDetailsView';
interface AchievementCategoryViewProps interface AchievementCategoryViewProps
{ {
category: AchievementCategory; category: AchievementCategory;
selectedAchievementId: number;
setSelectedAchievementId: Dispatch<SetStateAction<number>>;
setAchievementSeen: (code: string, achievementId: number) => void; setAchievementSeen: (code: string, achievementId: number) => void;
} }
export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props => export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props =>
{ {
const { category = null, setAchievementSeen = null } = props; const { category = null, selectedAchievementId = -1, setSelectedAchievementId = null, setAchievementSeen = null } = props;
const [ selectedAchievementId, setSelectedAchievementId ] = useState(0);
const getSelectedAchievement = useMemo(() => const selectedAchievement = useMemo(() =>
{ {
if(!category || !category.achievements.length) return null; if(selectedAchievementId === -1) return null;
return category.achievements.find(existing => (existing.achievementId === selectedAchievementId)); return category.achievements.find(achievement => (achievement.achievementId === selectedAchievementId));
}, [ category, selectedAchievementId ]); }, [ category, selectedAchievementId ]);
useEffect(() => useEffect(() =>
{ {
setSelectedAchievementId((!category || !category.achievements.length) ? 0 : category.achievements[0].achievementId); if(!selectedAchievement)
}, [ category ]); {
if(category.achievements.length) setSelectedAchievementId(category.achievements[0].achievementId);
}
}, [ selectedAchievement, category, setSelectedAchievementId ]);
useEffect(() => useEffect(() =>
{ {
if(!getSelectedAchievement || !getSelectedAchievement.unseen) return; if(!selectedAchievement) return;
setAchievementSeen(category.code, getSelectedAchievement.achievementId); setAchievementSeen(category.code, selectedAchievement.achievementId);
}, [ category, getSelectedAchievement, setAchievementSeen ]); }, [ selectedAchievement, category, setAchievementSeen ]);
if(!category) return null; if(!category) return null;
return ( return (
<Column fullHeight justifyContent="between"> <Column fullHeight justifyContent="between">
<AchievementListView achievements={ category.achievements } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } /> <AchievementListView achievements={ category.achievements } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } />
{ getSelectedAchievement && { !!selectedAchievement &&
<AchievementDetailsView achievement={ getSelectedAchievement } /> } <AchievementDetailsView achievement={ selectedAchievement } /> }
</Column> </Column>
); );
} }

View File

@ -11,12 +11,12 @@ interface AchievementDetailsViewProps
export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props => export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
{ {
const { achievement = null } = props; const { achievement = null, children = null, ...rest } = props;
if(!achievement) return null; if(!achievement) return null;
return ( return (
<Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden"> <Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden" { ...rest }>
<Column center gap={ 1 }> <Column center gap={ 1 }>
<AchievementBadgeView className="nitro-achievements-badge-image" achievement={ achievement } scale={ 2 } /> <AchievementBadgeView className="nitro-achievements-badge-image" achievement={ achievement } scale={ 2 } />
<Text fontWeight="bold"> <Text fontWeight="bold">
@ -48,6 +48,7 @@ export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
<LayoutProgressBar text={ LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) } progress={ (achievement.currentPoints + achievement.scoreAtStartOfLevel) } maxProgress={ (achievement.scoreLimit + achievement.scoreAtStartOfLevel) } /> } <LayoutProgressBar text={ LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) } progress={ (achievement.currentPoints + achievement.scoreAtStartOfLevel) } maxProgress={ (achievement.scoreLimit + achievement.scoreAtStartOfLevel) } /> }
</Column> } </Column> }
</Column> </Column>
{ children }
</Flex> </Flex>
) )
} }

View File

@ -1,21 +1,23 @@
import { AchievementData } from '@nitrots/nitro-renderer'; import { AchievementData } from '@nitrots/nitro-renderer';
import { FC } from 'react'; import { Dispatch, FC, SetStateAction } from 'react';
import { LayoutGridItem, LayoutGridItemProps } from '../../../../common'; import { LayoutGridItem } from '../../../../common';
import { AchievementBadgeView } from '../AchievementBadgeView'; import { AchievementBadgeView } from '../AchievementBadgeView';
interface AchievementListItemViewProps extends LayoutGridItemProps interface AchievementListItemViewProps
{ {
achievement: AchievementData; achievement: AchievementData;
selectedAchievementId: number;
setSelectedAchievementId: Dispatch<SetStateAction<number>>;
} }
export const AchievementListItemView: FC<AchievementListItemViewProps> = props => export const AchievementListItemView: FC<AchievementListItemViewProps> = props =>
{ {
const { achievement = null, children = null, ...rest } = props; const { achievement = null, selectedAchievementId = -1, setSelectedAchievementId = null, children = null, ...rest } = props;
if(!achievement) return null; if(!achievement) return null;
return ( return (
<LayoutGridItem itemCount={ achievement.unseen } itemCountMinimum={ 0 } { ...rest }> <LayoutGridItem itemActive={ (selectedAchievementId === achievement.achievementId) } itemUnseen={ (achievement.unseen > 0) } onClick={ event => setSelectedAchievementId(achievement.achievementId) } { ...rest }>
<AchievementBadgeView achievement={ achievement } /> <AchievementBadgeView achievement={ achievement } />
{ children } { children }
</LayoutGridItem> </LayoutGridItem>

View File

@ -3,7 +3,7 @@ import { Dispatch, FC, SetStateAction } from 'react';
import { AutoGrid } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AchievementListItemView } from './AchievementListItemView'; import { AchievementListItemView } from './AchievementListItemView';
export interface AchievementListViewProps interface AchievementListViewProps
{ {
achievements: AchievementData[]; achievements: AchievementData[];
selectedAchievementId: number; selectedAchievementId: number;
@ -12,11 +12,11 @@ export interface AchievementListViewProps
export const AchievementListView: FC<AchievementListViewProps> = props => export const AchievementListView: FC<AchievementListViewProps> = props =>
{ {
const { achievements = null, selectedAchievementId = 0, setSelectedAchievementId = null, children = null } = props; const { achievements = null, selectedAchievementId = -1, setSelectedAchievementId = null, children = null, ...rest } = props;
return ( return (
<AutoGrid columnCount={ 6 } columnMinWidth={ 50 } columnMinHeight={ 50 }> <AutoGrid columnCount={ 6 } columnMinWidth={ 50 } columnMinHeight={ 50 } { ...rest }>
{ achievements && (achievements.length > 0) && achievements.map((achievement, index) => <AchievementListItemView key={ index } achievement={ achievement } itemActive={ (selectedAchievementId === achievement.achievementId) } itemUnseen={ (achievement.unseen > 0) } onClick={ event => setSelectedAchievementId(achievement.achievementId) } />) } { achievements && (achievements.length > 0) && achievements.map((achievement, index) => <AchievementListItemView key={ index } achievement={ achievement } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } />) }
{ children } { children }
</AutoGrid> </AutoGrid>
); );

View File

@ -1,15 +1,19 @@
import { FC } from 'react'; import { Dispatch, FC, SetStateAction } from 'react';
import { GetAchievementCategoryImageUrl, GetAchievementCategoryMaxProgress, GetAchievementCategoryProgress, GetAchievementCategoryTotalUnseen, IAchievementCategory, LocalizeText } from '../../../../api'; import { GetAchievementCategoryImageUrl, GetAchievementCategoryMaxProgress, GetAchievementCategoryProgress, GetAchievementCategoryTotalUnseen, IAchievementCategory, LocalizeText } from '../../../../api';
import { LayoutBackgroundImage, LayoutGridItem, LayoutGridItemProps, Text } from '../../../../common'; import { LayoutBackgroundImage, LayoutGridItem, Text } from '../../../../common';
export interface AchievementCategoryListItemViewProps extends LayoutGridItemProps interface AchievementCategoryListItemViewProps
{ {
category: IAchievementCategory; category: IAchievementCategory;
selectedCategoryCode: string;
setSelectedCategoryCode: Dispatch<SetStateAction<string>>;
} }
export const AchievementsCategoryListItemView: FC<AchievementCategoryListItemViewProps> = props => export const AchievementsCategoryListItemView: FC<AchievementCategoryListItemViewProps> = props =>
{ {
const { category = null, children = null, ...rest } = props; const { category = null, selectedCategoryCode = null, setSelectedCategoryCode = null, children = null, ...rest } = props;
if(!category) return null;
const progress = GetAchievementCategoryProgress(category); const progress = GetAchievementCategoryProgress(category);
const maxProgress = GetAchievementCategoryMaxProgress(category); const maxProgress = GetAchievementCategoryMaxProgress(category);
@ -17,9 +21,9 @@ export const AchievementsCategoryListItemView: FC<AchievementCategoryListItemVie
const getTotalUnseen = GetAchievementCategoryTotalUnseen(category); const getTotalUnseen = GetAchievementCategoryTotalUnseen(category);
return ( return (
<LayoutGridItem itemCount={ getTotalUnseen } itemCountMinimum={ 0 } gap={ 1 } { ...rest }> <LayoutGridItem itemActive={ (selectedCategoryCode === category.code) } itemCount={ getTotalUnseen } itemCountMinimum={ 0 } gap={ 1 } onClick={ event => setSelectedCategoryCode(category.code) } { ...rest }>
<Text fullWidth center className="small pt-1">{ LocalizeText(`quests.${ category.code }.name`) }</Text> <Text fullWidth center small className="pt-1">{ LocalizeText(`quests.${ category.code }.name`) }</Text>
<LayoutBackgroundImage className="position-relative" imageUrl={ getCategoryImage }> <LayoutBackgroundImage position="relative" imageUrl={ getCategoryImage }>
<Text fullWidth center position="absolute" variant="white" style={ { fontSize: 12, bottom: 9 } }>{ progress } / { maxProgress }</Text> <Text fullWidth center position="absolute" variant="white" style={ { fontSize: 12, bottom: 9 } }>{ progress } / { maxProgress }</Text>
</LayoutBackgroundImage> </LayoutBackgroundImage>
{ children } { children }

View File

@ -1,9 +1,9 @@
import { Dispatch, FC, SetStateAction } from 'react'; import { Dispatch, FC, SetStateAction } from 'react';
import { IAchievementCategory } from '../../../../api'; import { IAchievementCategory } from '../../../../api';
import { AutoGrid, AutoGridProps } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AchievementsCategoryListItemView } from './AchievementsCategoryListItemView'; import { AchievementsCategoryListItemView } from './AchievementsCategoryListItemView';
export interface AchievementsCategoryListViewProps extends AutoGridProps interface AchievementsCategoryListViewProps
{ {
categories: IAchievementCategory[]; categories: IAchievementCategory[];
selectedCategoryCode: string; selectedCategoryCode: string;
@ -12,11 +12,11 @@ export interface AchievementsCategoryListViewProps extends AutoGridProps
export const AchievementsCategoryListView: FC<AchievementsCategoryListViewProps> = props => export const AchievementsCategoryListView: FC<AchievementsCategoryListViewProps> = props =>
{ {
const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, columnCount = 3, columnMinWidth = 90, columnMinHeight = 100, children = null, ...rest } = props; const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, children = null, ...rest } = props;
return ( return (
<AutoGrid columnCount={ columnCount } columnMinWidth={ columnMinWidth } columnMinHeight={ columnMinHeight } { ...rest }> <AutoGrid columnCount={ 3 } columnMinWidth={ 90 } columnMinHeight={ 100 } { ...rest }>
{ categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } itemActive={ (selectedCategoryCode === category.code) } onClick={ event => setSelectedCategoryCode(category.code) } /> ) } { categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } /> ) }
{ children } { children }
</AutoGrid> </AutoGrid>
); );

View File

@ -1,9 +1,8 @@
import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { GetConfiguration, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../api'; import { CreateLinkEvent, GetConfiguration, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../api';
import { Button, Column, Flex, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { Button, Column, Flex, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { InventoryEvent } from '../../../../events'; import { UseMessageEventHook } from '../../../../hooks';
import { DispatchUiEvent, UseMessageEventHook } from '../../../../hooks';
export interface CameraWidgetCheckoutViewProps export interface CameraWidgetCheckoutViewProps
{ {
@ -122,7 +121,7 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
{ (picturesBought > 0) && { (picturesBought > 0) &&
<Text> <Text>
<Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought } <Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought }
<u className="ms-1 cursor-pointer" onClick={ () => DispatchUiEvent(new InventoryEvent(InventoryEvent.SHOW_INVENTORY)) }>{ LocalizeText('camera.open.inventory') }</u> <u className="ms-1 cursor-pointer" onClick={ () => CreateLinkEvent('inventory/open') }>{ LocalizeText('camera.open.inventory') }</u>
</Text> } </Text> }
</Column> </Column>
<Flex alignItems="center"> <Flex alignItems="center">

View File

@ -1,10 +1,9 @@
import { BadgesEvent, ClubGiftInfoEvent, FigureUpdateEvent, FriendlyTime, GetClubGiftInfo, ILinkEventTracker, RequestBadgesComposer, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent, UserInfoEvent, UserSubscriptionEvent } from '@nitrots/nitro-renderer'; import { BadgesEvent, ClubGiftInfoEvent, FriendlyTime, GetClubGiftInfo, ILinkEventTracker, RequestBadgesComposer, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent, UserSubscriptionEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { OverlayTrigger, Popover } from 'react-bootstrap'; import { OverlayTrigger, Popover } from 'react-bootstrap';
import { AddEventLinkTracker, CreateLinkEvent, GetConfiguration, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../api'; import { AddEventLinkTracker, CreateLinkEvent, GetConfiguration, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../api';
import { Base, Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; import { Base, Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { HcCenterEvent } from '../../events'; import { UseMessageEventHook, useSessionInfo } from '../../hooks';
import { UseMessageEventHook, UseUiEvent } from '../../hooks';
import { BadgeResolver } from './common/BadgeResolver'; import { BadgeResolver } from './common/BadgeResolver';
import { ClubStatus } from './common/ClubStatus'; import { ClubStatus } from './common/ClubStatus';
@ -12,7 +11,6 @@ import { ClubStatus } from './common/ClubStatus';
export const HcCenterView: FC<{}> = props => export const HcCenterView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const [ userFigure, setUserFigure ] = useState<string>(null);
const [ kickbackData, setKickbackData ] = useState<ScrKickbackData>(null); const [ kickbackData, setKickbackData ] = useState<ScrKickbackData>(null);
const [ clubDays, setClubDays ] = useState(0); const [ clubDays, setClubDays ] = useState(0);
const [ pastClubDays, setPastClubDays ] = useState(0); const [ pastClubDays, setPastClubDays ] = useState(0);
@ -21,6 +19,7 @@ export const HcCenterView: FC<{}> = props =>
const [ clubStatus, setClubStatus ] = useState(ClubStatus.NONE); const [ clubStatus, setClubStatus ] = useState(ClubStatus.NONE);
const [ unclaimedGifts, setUnclaimedGifts ] = useState(0); const [ unclaimedGifts, setUnclaimedGifts ] = useState(0);
const [ badgeCode, setBadgeCode ] = useState(BadgeResolver.default_badge); const [ badgeCode, setBadgeCode ] = useState(BadgeResolver.default_badge);
const { userFigure = null } = useSessionInfo();
const getClubText = () => const getClubText = () =>
{ {
@ -64,36 +63,6 @@ export const HcCenterView: FC<{}> = props =>
return LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData.creditRewardForStreakBonus + kickbackData.creditRewardForMonthlySpent).toString() ]); return LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData.creditRewardForStreakBonus + kickbackData.creditRewardForMonthlySpent).toString() ]);
} }
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
setUserFigure(parser.userInfo.figure);
}, []);
UseMessageEventHook(UserInfoEvent, onUserInfoEvent);
const onUserFigureEvent = useCallback((event: FigureUpdateEvent) =>
{
const parser = event.getParser();
setUserFigure(parser.figure);
}, []);
UseMessageEventHook(FigureUpdateEvent, onUserFigureEvent);
const onHcCenterEvent = useCallback((event: HcCenterEvent) =>
{
switch(event.type)
{
case HcCenterEvent.TOGGLE_HC_CENTER:
setIsVisible(!isVisible);
break;
}
}, [ isVisible ]);
UseUiEvent(HcCenterEvent.TOGGLE_HC_CENTER, onHcCenterEvent);
const onClubGiftInfoEvent = useCallback((event: ClubGiftInfoEvent) => const onClubGiftInfoEvent = useCallback((event: ClubGiftInfoEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
@ -265,7 +234,7 @@ export const HcCenterView: FC<{}> = props =>
<h5 className="ms-2 mb-1 bolder">{LocalizeText('hccenter.special.amount.title')}</h5> <h5 className="ms-2 mb-1 bolder">{LocalizeText('hccenter.special.amount.title')}</h5>
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<div className="w-100 text-center ms-4n">{getHcPaydayAmount()}</div> <div className="w-100 text-center ms-4n">{getHcPaydayAmount()}</div>
<OverlayTrigger trigger="hover" placement="left" overlay={popover}> <OverlayTrigger trigger={ [ 'hover', 'focus' ] } placement="left" overlay={popover}>
<div className="btn btn-link align-self-end text-primary"> <div className="btn btn-link align-self-end text-primary">
{LocalizeText('hccenter.breakdown.infolink')} {LocalizeText('hccenter.breakdown.infolink')}
</div> </div>

View File

@ -1,36 +1,16 @@
import { FigureUpdateEvent, RoomSessionEvent, UserInfoDataParser, UserInfoEvent } from '@nitrots/nitro-renderer'; import { RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { GetConfiguration, GetConfigurationManager } from '../../api'; import { GetConfiguration, GetConfigurationManager } from '../../api';
import { LayoutAvatarImageView } from '../../common'; import { LayoutAvatarImageView } from '../../common';
import { UseMessageEventHook, UseRoomSessionManagerEvent } from '../../hooks'; import { UseRoomSessionManagerEvent, useSessionInfo } from '../../hooks';
import { WidgetSlotView } from './views/widgets/WidgetSlotView'; import { WidgetSlotView } from './views/widgets/WidgetSlotView';
const widgetSlotCount = 7;
export const HotelView: FC<{}> = props => export const HotelView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(true); const [ isVisible, setIsVisible ] = useState(true);
const widgetSlotCount = 7; const { userFigure = null } = useSessionInfo();
const [ userFigure, setUserFigure ] = useState<string>(null);
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
setUserInfo(parser.userInfo);
setUserFigure(parser.userInfo.figure);
}, []);
UseMessageEventHook(UserInfoEvent, onUserInfoEvent);
const onUserFigureEvent = useCallback((event: FigureUpdateEvent) =>
{
const parser = event.getParser();
setUserFigure(parser.figure);
}, []);
UseMessageEventHook(FigureUpdateEvent, onUserFigureEvent);
const onRoomSessionEvent = useCallback((event: RoomSessionEvent) => const onRoomSessionEvent = useCallback((event: RoomSessionEvent) =>
{ {
@ -51,12 +31,12 @@ export const HotelView: FC<{}> = props =>
if(!isVisible) return null; if(!isVisible) return null;
const backgroundColor = GetConfiguration('hotelview')['images']['background.colour']; const backgroundColor = GetConfiguration('hotelview')['images']['background.colour'];
const background = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['background']); const background = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['background']);
const sun = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['sun']); const sun = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['sun']);
const drape = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['drape']); const drape = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['drape']);
const left = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['left']); const left = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['left']);
const rightRepeat = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['right.repeat']); const rightRepeat = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['right.repeat']);
const right = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['right']); const right = GetConfigurationManager().interpolate(GetConfiguration('hotelview')['images']['right']);
return ( return (
<div className="nitro-hotel-view" style={(backgroundColor && backgroundColor) ? { background: backgroundColor } : {}}> <div className="nitro-hotel-view" style={(backgroundColor && backgroundColor) ? { background: backgroundColor } : {}}>

View File

@ -8,7 +8,7 @@ export const InventoryBadgeView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const { badgeCodes = [], activeBadgeCodes = [], selectedBadgeCode = null, isWearingBadge = null, canWearBadges = null, toggleBadge = null, getBadgeId = null, activate = null, deactivate = null } = useInventoryBadges(); const { badgeCodes = [], activeBadgeCodes = [], selectedBadgeCode = null, isWearingBadge = null, canWearBadges = null, toggleBadge = null, getBadgeId = null, activate = null, deactivate = null } = useInventoryBadges();
const { getCount = null, resetCategory = null, isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker(); const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker();
useEffect(() => useEffect(() =>
{ {

View File

@ -70,9 +70,9 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
<Flex gap={2} overflow="hidden"> <Flex gap={2} overflow="hidden">
<LayoutRoomThumbnailView roomId={roomData.roomId} customUrl={roomData.officialRoomPicRef} className="d-flex flex-column align-items-center justify-content-end mb-1"> <LayoutRoomThumbnailView roomId={roomData.roomId} customUrl={roomData.officialRoomPicRef} className="d-flex flex-column align-items-center justify-content-end mb-1">
{roomData.habboGroupId > 0 && ( {roomData.habboGroupId > 0 && (
<LayoutBadgeImageView badgeCode={roomData.groupBadgeCode} isGroup={true} className={"position-absolute top-0 start-0 m-1 "}/>)} <LayoutBadgeImageView badgeCode={roomData.groupBadgeCode} isGroup={true} className={'position-absolute top-0 start-0 m-1 '}/>)}
{roomData.doorMode !== RoomDataParser.OPEN_STATE && ( {roomData.doorMode !== RoomDataParser.OPEN_STATE && (
<i className={"position-absolute end-0 mb-1 me-1 icon icon-navigator-room-" + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? "locked" : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? "password" : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? "invisible" : "")}/> )} <i className={'position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '')}/> )}
</LayoutRoomThumbnailView> </LayoutRoomThumbnailView>
<Column gap={1}> <Column gap={1}>
<Text bold truncate className="flex-grow-1" style={{ maxHeight: 13 }}> <Text bold truncate className="flex-grow-1" style={{ maxHeight: 13 }}>
@ -80,7 +80,7 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
</Text> </Text>
<Flex gap={1}> <Flex gap={1}>
<Text italics variant="muted"> <Text italics variant="muted">
{LocalizeText("navigator.roomownercaption")} {LocalizeText('navigator.roomownercaption')}
</Text> </Text>
<UserProfileIconView <UserProfileIconView
userId={roomData.ownerId} userId={roomData.ownerId}
@ -90,7 +90,7 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
<Text className="flex-grow-1"> <Text className="flex-grow-1">
{roomData.description} {roomData.description}
</Text> </Text>
<Flex className={"badge p-1 position-absolute m-1 bottom-0 end-0 m-2 " + getUserCounterColor()} gap={1}> <Flex className={'badge p-1 position-absolute m-1 bottom-0 end-0 m-2 ' + getUserCounterColor()} gap={1}>
<FontAwesomeIcon icon="user" /> <FontAwesomeIcon icon="user" />
{roomData.userCount} {roomData.userCount}
</Flex> </Flex>

View File

@ -2,7 +2,7 @@ import { ActivityPointNotificationMessageEvent, FriendlyTime, HabboClubLevelEnum
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { CreateLinkEvent, GetConfiguration, LocalizeText, PlaySound, SendMessageComposer, SoundNames } from '../../api'; import { CreateLinkEvent, GetConfiguration, LocalizeText, PlaySound, SendMessageComposer, SoundNames } from '../../api';
import { Column, Flex, Grid, LayoutCurrencyIcon, Text } from '../../common'; import { Column, Flex, Grid, LayoutCurrencyIcon, Text } from '../../common';
import { HcCenterEvent, UserSettingsUIEvent } from '../../events'; import { UserSettingsUIEvent } from '../../events';
import { DispatchUiEvent, UseMessageEventHook } from '../../hooks'; import { DispatchUiEvent, UseMessageEventHook } from '../../hooks';
import { IPurse } from './common/IPurse'; import { IPurse } from './common/IPurse';
import { Purse } from './common/Purse'; import { Purse } from './common/Purse';
@ -179,7 +179,7 @@ export const PurseView: FC<{}> = props =>
{ getCurrencyElements(0, 2) } { getCurrencyElements(0, 2) }
</Column> </Column>
{ !hcDisabled && { !hcDisabled &&
<Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ event => DispatchUiEvent(new HcCenterEvent(HcCenterEvent.TOGGLE_HC_CENTER)) }> <Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ event => CreateLinkEvent('habboUI/open/hccenter') }>
<LayoutCurrencyIcon type="hc" /> <LayoutCurrencyIcon type="hc" />
<Text variant="white">{ getClubText }</Text> <Text variant="white">{ getClubText }</Text>
</Column> } </Column> }

View File

@ -1,44 +1,24 @@
import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer'; import { Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { CreateLinkEvent, GetSessionDataManager, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; import { CreateLinkEvent, GetSessionDataManager, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api';
import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common'; import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common';
import { AchievementsUIUnseenCountEvent, ModToolsEvent } from '../../events'; import { ModToolsEvent } from '../../events';
import { DispatchUiEvent, useFriends, useInventoryUnseenTracker, UseMessageEventHook, useMessenger, UseRoomEngineEvent, UseUiEvent } from '../../hooks'; import { DispatchUiEvent, useAchievements, useFriends, useInventoryUnseenTracker, UseMessageEventHook, useMessenger, UseRoomEngineEvent, useSessionInfo } from '../../hooks';
import { ToolbarMeView } from './ToolbarMeView'; import { ToolbarMeView } from './ToolbarMeView';
export const ToolbarView: FC<{ isInRoom: boolean }> = props => export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
{ {
const { isInRoom } = props; const { isInRoom } = props;
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ userFigure, setUserFigure ] = useState<string>(null);
const [ isMeExpanded, setMeExpanded ] = useState(false); const [ isMeExpanded, setMeExpanded ] = useState(false);
const [ useGuideTool, setUseGuideTool ] = useState(false); const [ useGuideTool, setUseGuideTool ] = useState(false);
const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0); const { userFigure = null } = useSessionInfo();
const { getFullCount = null } = useInventoryUnseenTracker(); const { getFullCount = 0 } = useInventoryUnseenTracker();
const { getTotalUnseen = 0 } = useAchievements();
const { requests = [] } = useFriends(); const { requests = [] } = useFriends();
const { iconState = MessengerIconState.HIDDEN } = useMessenger(); const { iconState = MessengerIconState.HIDDEN } = useMessenger();
const isMod = GetSessionDataManager().isModerator; const isMod = GetSessionDataManager().isModerator;
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
setUserInfo(parser.userInfo);
setUserFigure(parser.userInfo.figure);
}, []);
UseMessageEventHook(UserInfoEvent, onUserInfoEvent);
const onUserFigureEvent = useCallback((event: FigureUpdateEvent) =>
{
const parser = event.getParser();
setUserFigure(parser.figure);
}, []);
UseMessageEventHook(FigureUpdateEvent, onUserFigureEvent);
const onPerkAllowancesMessageEvent = useCallback((event: PerkAllowancesMessageEvent) => const onPerkAllowancesMessageEvent = useCallback((event: PerkAllowancesMessageEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
@ -48,13 +28,6 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
UseMessageEventHook(PerkAllowancesMessageEvent, onPerkAllowancesMessageEvent); UseMessageEventHook(PerkAllowancesMessageEvent, onPerkAllowancesMessageEvent);
const onAchievementsUIUnseenCountEvent = useCallback((event: AchievementsUIUnseenCountEvent) =>
{
setUnseenAchievementCount(event.count);
}, []);
UseUiEvent(AchievementsUIUnseenCountEvent.UNSEEN_COUNT, onAchievementsUIUnseenCountEvent);
const animationIconToToolbar = useCallback((iconName: string, image: HTMLImageElement, x: number, y: number) => const animationIconToToolbar = useCallback((iconName: string, image: HTMLImageElement, x: number, y: number) =>
{ {
const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement); const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement);
@ -96,20 +69,18 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
UseRoomEngineEvent(NitroToolbarAnimateIconEvent.ANIMATE_ICON, onNitroToolbarAnimateIconEvent); UseRoomEngineEvent(NitroToolbarAnimateIconEvent.ANIMATE_ICON, onNitroToolbarAnimateIconEvent);
const unseenInventoryCount = getFullCount();
return ( return (
<> <>
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }> <TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>
<ToolbarMeView useGuideTool={ useGuideTool } unseenAchievementCount={ unseenAchievementCount } setMeExpanded={ setMeExpanded } /> <ToolbarMeView useGuideTool={ useGuideTool } unseenAchievementCount={ getTotalUnseen } setMeExpanded={ setMeExpanded } />
</TransitionAnimation> </TransitionAnimation>
<Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3"> <Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3">
<Flex gap={ 2 } alignItems="center"> <Flex gap={ 2 } alignItems="center">
<Flex alignItems="center" gap={ 2 }> <Flex alignItems="center" gap={ 2 }>
<Flex center pointer className={ 'navigation-item item-avatar ' + (isMeExpanded ? 'active ' : '') } onClick={ event => setMeExpanded(!isMeExpanded) }> <Flex center pointer className={ 'navigation-item item-avatar ' + (isMeExpanded ? 'active ' : '') } onClick={ event => setMeExpanded(!isMeExpanded) }>
<LayoutAvatarImageView figure={ userFigure } direction={ 2 } /> <LayoutAvatarImageView figure={ userFigure } direction={ 2 } />
{ (unseenAchievementCount > 0) && { (getTotalUnseen > 0) &&
<LayoutItemCountView count={ unseenAchievementCount } /> } <LayoutItemCountView count={ getTotalUnseen } /> }
</Flex> </Flex>
{ isInRoom && { isInRoom &&
<Base pointer className="navigation-item icon icon-habbo" onClick={ event => VisitDesktop() } /> } <Base pointer className="navigation-item icon icon-habbo" onClick={ event => VisitDesktop() } /> }
@ -118,8 +89,8 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
<Base pointer className="navigation-item icon icon-rooms" onClick={ event => CreateLinkEvent('navigator/toggle') } /> <Base pointer className="navigation-item icon icon-rooms" onClick={ event => CreateLinkEvent('navigator/toggle') } />
<Base pointer className="navigation-item icon icon-catalog" onClick={ event => CreateLinkEvent('catalog/toggle') } /> <Base pointer className="navigation-item icon icon-catalog" onClick={ event => CreateLinkEvent('catalog/toggle') } />
<Base pointer className="navigation-item icon icon-inventory" onClick={ event => CreateLinkEvent('inventory/toggle') }> <Base pointer className="navigation-item icon icon-inventory" onClick={ event => CreateLinkEvent('inventory/toggle') }>
{ (unseenInventoryCount > 0) && { (getFullCount > 0) &&
<LayoutItemCountView count={ unseenInventoryCount } /> } <LayoutItemCountView count={ getFullCount } /> }
</Base> </Base>
{ isInRoom && { isInRoom &&
<Base pointer className="navigation-item icon icon-camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> } <Base pointer className="navigation-item icon icon-camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> }

View File

@ -1,20 +0,0 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class AchievementsUIUnseenCountEvent extends NitroEvent
{
public static UNSEEN_COUNT: string = 'AUUCE_UNSEEN_COUNT';
private _count: number;
constructor(count: number)
{
super(AchievementsUIUnseenCountEvent.UNSEEN_COUNT);
this._count = count;
}
public get count(): number
{
return this._count;
}
}

View File

@ -1 +0,0 @@
export * from './AchievementsUIUnseenCountEvent';

View File

@ -1,48 +0,0 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class FriendEnteredRoomEvent extends NitroEvent
{
public static ENTERED: string = 'FERE_ENTERED';
private _roomIndex: number;
private _category: number;
private _id: number;
private _name: string;
private _userType: number;
constructor(roomIndex: number, category: number, id: number, name: string, userType: number)
{
super(FriendEnteredRoomEvent.ENTERED);
this._roomIndex = roomIndex;
this._category = category;
this._id = id;
this._name = name;
this._userType = userType;
}
public get roomIndex(): number
{
return this._roomIndex;
}
public get category(): number
{
return this._category;
}
public get id(): number
{
return this._id;
}
public get name(): string
{
return this._name;
}
public get userType(): number
{
return this._userType;
}
}

View File

@ -1 +0,0 @@
export * from './FriendEnteredRoomEvent';

View File

@ -1,6 +0,0 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class HcCenterEvent extends NitroEvent
{
public static TOGGLE_HC_CENTER: string = 'HCC_TOGGLE';
}

View File

@ -1 +0,0 @@
export * from './HcCenterEvent';

View File

@ -1,11 +1,7 @@
export * from './achievements';
export * from './catalog'; export * from './catalog';
export * from './floorplan-editor'; export * from './floorplan-editor';
export * from './friends';
export * from './guide-tool'; export * from './guide-tool';
export * from './hc-center';
export * from './help'; export * from './help';
export * from './inventory';
export * from './mod-tools'; export * from './mod-tools';
export * from './notification-center'; export * from './notification-center';
export * from './room-widgets'; export * from './room-widgets';

View File

@ -1,8 +0,0 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class InventoryEvent extends NitroEvent
{
public static SHOW_INVENTORY: string = 'IE_SHOW_INVENTORY';
public static HIDE_INVENTORY: string = 'IE_HIDE_INVENTORY';
public static TOGGLE_INVENTORY: string = 'IE_TOGGLE_INVENTORY';
}

View File

@ -1,27 +0,0 @@
import { InventoryEvent } from './InventoryEvent';
export class InventoryTradeRequestEvent extends InventoryEvent
{
public static REQUEST_TRADE: string = 'ITSE_REQUEST_TRADE';
private _objectId: number;
private _username: string;
constructor(objectId: number, username: string)
{
super(InventoryTradeRequestEvent.REQUEST_TRADE);
this._objectId = objectId;
this._username = username;
}
public get objectId(): number
{
return this._objectId;
}
public get username(): string
{
return this._username;
}
}

View File

@ -1,28 +0,0 @@
import { TradeUserData } from '../../api';
import { InventoryEvent } from './InventoryEvent';
export class InventoryTradeStartEvent extends InventoryEvent
{
public static START_TRADE: string = 'ITSE_START_TRADE';
private _ownUserTradeData: TradeUserData;
private _otherUserTradeData: TradeUserData;
constructor(ownUserTradeData: TradeUserData, otherUserTradeData: TradeUserData)
{
super(InventoryTradeStartEvent.START_TRADE);
this._ownUserTradeData = ownUserTradeData;
this._otherUserTradeData = otherUserTradeData;
}
public get ownUserTradeData(): TradeUserData
{
return this._ownUserTradeData;
}
public get otherUserTradeData(): TradeUserData
{
return this._otherUserTradeData;
}
}

View File

@ -1,3 +0,0 @@
export * from './InventoryEvent';
export * from './InventoryTradeRequestEvent';
export * from './InventoryTradeStartEvent';

View File

@ -0,0 +1 @@
export * from './useAchievements';

View File

@ -0,0 +1,169 @@
import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { AchievementCategory, CloneObject, GetAchievementCategoryTotalUnseen, GetAchievementIsIgnored, SendMessageComposer } from '../../api';
import { UseMessageEventHook } from '../messages';
const useAchievementsState = () =>
{
const [ needsUpdate, setNeedsUpdate ] = useState<boolean>(true);
const [ achievementCategories, setAchievementCategories ] = useState<AchievementCategory[]>([]);
const [ selectedCategoryCode, setSelectedCategoryCode ] = useState<string>(null);
const [ selectedAchievementId, setSelectedAchievementId ] = useState<number>(-1);
const [ achievementScore, setAchievementScore ] = useState<number>(0);
const getTotalUnseen = useMemo(() =>
{
let unseen = 0;
achievementCategories.forEach(category => unseen += GetAchievementCategoryTotalUnseen(category));
return unseen;
}, [ achievementCategories ]);
const getProgress = useMemo(() =>
{
let progress = 0;
achievementCategories.forEach(category => (progress += category.getProgress()));
return progress;
}, [ achievementCategories ]);
const getMaxProgress = useMemo(() =>
{
let progress = 0;
achievementCategories.forEach(category => (progress += category.getMaxProgress()));
return progress;
}, [ achievementCategories ]);
const scaledProgressPercent = useMemo(() =>
{
return ~~((((getProgress - 0) * (100 - 0)) / (getMaxProgress - 0)) + 0);
}, [ getProgress, getMaxProgress ]);
const setAchievementSeen = useCallback((categoryCode: string, achievementId: number) =>
{
setAchievementCategories(prevValue =>
{
const newValue = [ ...prevValue ];
for(const category of newValue)
{
if(category.code !== categoryCode) continue;
for(const achievement of category.achievements)
{
if(achievement.achievementId !== achievementId) continue;
achievement.unseen = 0;
}
}
return newValue;
});
}, []);
const onAchievementEvent = useCallback((event: AchievementEvent) =>
{
const parser = event.getParser();
const achievement = parser.achievement;
setAchievementCategories(prevValue =>
{
const newValue = [ ...prevValue ];
const categoryIndex = newValue.findIndex(existing => (existing.code === achievement.category));
if(categoryIndex === -1)
{
const category = new AchievementCategory(achievement.category);
category.achievements.push(achievement);
newValue.push(category);
}
else
{
const category = CloneObject(newValue[categoryIndex]);
const newAchievements = [ ...category.achievements ];
const achievementIndex = newAchievements.findIndex(existing => (existing.achievementId === achievement.achievementId));
let previousAchievement: AchievementData = null;
if(achievementIndex === -1)
{
newAchievements.push(achievement);
}
else
{
previousAchievement = newAchievements[achievementIndex];
newAchievements[achievementIndex] = achievement;
}
if(!GetAchievementIsIgnored(achievement))
{
achievement.unseen++;
if(previousAchievement) achievement.unseen += previousAchievement.unseen;
}
category.achievements = newAchievements;
newValue[categoryIndex] = category;
}
return newValue;
});
}, []);
UseMessageEventHook(AchievementEvent, onAchievementEvent);
const onAchievementsEvent = useCallback((event: AchievementsEvent) =>
{
const parser = event.getParser();
const categories: AchievementCategory[] = [];
for(const achievement of parser.achievements)
{
const categoryName = achievement.category;
let existing = categories.find(category => (category.code === categoryName));
if(!existing)
{
existing = new AchievementCategory(categoryName);
categories.push(existing);
}
existing.achievements.push(achievement);
}
setAchievementCategories(categories);
}, []);
UseMessageEventHook(AchievementsEvent, onAchievementsEvent);
const onAchievementsScoreEvent = useCallback((event: AchievementsScoreEvent) =>
{
const parser = event.getParser();
setAchievementScore(parser.score);
}, []);
UseMessageEventHook(AchievementsScoreEvent, onAchievementsScoreEvent);
useEffect(() =>
{
if(!needsUpdate) return;
SendMessageComposer(new RequestAchievementsMessageComposer());
setNeedsUpdate(false);
}, [ needsUpdate ]);
return { achievementCategories, selectedCategoryCode, setSelectedCategoryCode, selectedAchievementId, setSelectedAchievementId, achievementScore, getTotalUnseen, getProgress, getMaxProgress, scaledProgressPercent, setAchievementSeen };
}
export const useAchievements = useAchievementsState;

View File

@ -1,3 +1,4 @@
export * from './achievements';
export * from './events'; export * from './events';
export * from './events/core'; export * from './events/core';
export * from './events/nitro'; export * from './events/nitro';
@ -6,5 +7,6 @@ export * from './friends';
export * from './inventory'; export * from './inventory';
export * from './messages'; export * from './messages';
export * from './navigator'; export * from './navigator';
export * from './session';
export * from './UseMountEffect'; export * from './UseMountEffect';
export * from './useSharedVisibility'; export * from './useSharedVisibility';

View File

@ -1,11 +1,9 @@
import { AdvancedMap, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCloseParser, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListItemEvent, TradingListItemRemoveComposer, TradingNotOpenEvent, TradingOpenComposer, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent } from '@nitrots/nitro-renderer'; import { AdvancedMap, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCloseParser, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListItemEvent, TradingListItemRemoveComposer, TradingNotOpenEvent, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useBetween } from 'use-between'; import { useBetween } from 'use-between';
import { useInventoryFurni } from '.'; import { useInventoryFurni } from '.';
import { UseMessageEventHook } from '..'; import { UseMessageEventHook } from '..';
import { CloneObject, GetRoomSession, GetSessionDataManager, GroupItem, LocalizeText, NotificationUtilities, parseTradeItems, SendMessageComposer, TradeState, TradeUserData, TradingNotificationMessage, TradingNotificationType } from '../../api'; import { CloneObject, GetRoomSession, GetSessionDataManager, GroupItem, LocalizeText, NotificationUtilities, parseTradeItems, SendMessageComposer, TradeState, TradeUserData, TradingNotificationMessage, TradingNotificationType } from '../../api';
import { InventoryTradeRequestEvent } from '../../events';
import { UseUiEvent } from '../events';
const useInventoryTradeState = () => const useInventoryTradeState = () =>
{ {
@ -66,20 +64,6 @@ const useInventoryTradeState = () =>
} }
} }
const onInventoryTradeRequestEvent = useCallback((event: InventoryTradeRequestEvent) =>
{
switch(event.type)
{
case InventoryTradeRequestEvent.REQUEST_TRADE: {
const tradeEvent = (event as InventoryTradeRequestEvent);
SendMessageComposer(new TradingOpenComposer(tradeEvent.objectId));
}
}
}, []);
UseUiEvent(InventoryTradeRequestEvent.REQUEST_TRADE, onInventoryTradeRequestEvent);
const onTradingAcceptEvent = useCallback((event: TradingAcceptEvent) => const onTradingAcceptEvent = useCallback((event: TradingAcceptEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();

View File

@ -1,5 +1,5 @@
import { UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer } from '@nitrots/nitro-renderer'; import { UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useBetween } from 'use-between'; import { useBetween } from 'use-between';
import { UseMessageEventHook } from '..'; import { UseMessageEventHook } from '..';
import { SendMessageComposer } from '../../api'; import { SendMessageComposer } from '../../api';
@ -10,17 +10,17 @@ const sendResetItemsMessage = (category: number, itemIds: number[]) => SendMessa
const useInventoryUnseenTrackerState = () => const useInventoryUnseenTrackerState = () =>
{ {
const [ unseenItems, setUnseenItems ] = useState<Map<number, number[]>>(new Map()); const [ unseenItems, setUnseenItems ] = useState<Map<number, number[]>>(new Map());
const getIds = (category: number) => unseenItems.get(category);
const getCount = (category: number) => (unseenItems.get(category)?.length || 0);
const getFullCount = () => const getCount = useCallback((category: number) => (unseenItems.get(category)?.length || 0), [ unseenItems ]);
const getFullCount = useMemo(() =>
{ {
let count = 0; let count = 0;
for(const key of unseenItems.keys()) count += getCount(key); for(const key of unseenItems.keys()) count += getCount(key);
return count; return count;
} }, [ unseenItems, getCount ]);
const resetCategory = useCallback((category: number) => const resetCategory = useCallback((category: number) =>
{ {
@ -128,7 +128,7 @@ const useInventoryUnseenTrackerState = () =>
UseMessageEventHook(UnseenItemsEvent, onUnseenItemsEvent); UseMessageEventHook(UnseenItemsEvent, onUnseenItemsEvent);
return { getIds, getCount, getFullCount, resetCategory, resetItems, isUnseen, removeUnseen }; return { getCount, getFullCount, resetCategory, resetItems, isUnseen, removeUnseen };
} }
export const useInventoryUnseenTracker = () => useBetween(useInventoryUnseenTrackerState); export const useInventoryUnseenTracker = () => useBetween(useInventoryUnseenTrackerState);

View File

@ -0,0 +1 @@
export * from './useSessionInfo';

View File

@ -0,0 +1,33 @@
import { FigureUpdateEvent, UserInfoDataParser, UserInfoEvent } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react';
import { useBetween } from 'use-between';
import { UseMessageEventHook } from '../messages';
const useSessionInfoState = () =>
{
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ userFigure, setUserFigure ] = useState<string>(null);
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
{
const parser = event.getParser();
setUserInfo(parser.userInfo);
setUserFigure(parser.userInfo.figure);
}, []);
UseMessageEventHook(UserInfoEvent, onUserInfoEvent);
const onUserFigureEvent = useCallback((event: FigureUpdateEvent) =>
{
const parser = event.getParser();
setUserFigure(parser.figure);
}, []);
UseMessageEventHook(FigureUpdateEvent, onUserFigureEvent);
return { userInfo, userFigure };
}
export const useSessionInfo = () => useBetween(useSessionInfoState);