Update notification-center

This commit is contained in:
Bill 2021-08-26 02:38:16 -04:00
parent 48dfdb6ce1
commit c0293357fb
20 changed files with 364 additions and 70 deletions

View File

@ -50,3 +50,11 @@ ul {
.w-unset {
width: unset;
}
.no-select {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}

View File

@ -0,0 +1,48 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class SimpleAlertUIEvent extends NitroEvent
{
public static ALERT: string = 'SAUE_ALERT';
private _message: string;
private _clickUrl: string;
private _clickUrlText: string;
private _title: string;
private _imageUrl: string;
constructor(message: string, clickUrl: string = null, clickUrlText: string = null, title: string = null, imageUrl: string = null)
{
super(SimpleAlertUIEvent.ALERT);
this._message = message;
this._clickUrl = clickUrl;
this._clickUrlText = clickUrlText;
this._title = title;
this._imageUrl = imageUrl;
}
public get message(): string
{
return this._message;
}
public get clickUrl(): string
{
return this._clickUrl;
}
public get clickUrlText(): string
{
return this._clickUrlText;
}
public get title(): string
{
return this._title;
}
public get imageUrl(): string
{
return this._imageUrl;
}
}

View File

@ -1,3 +1,4 @@
export * from './NotificationCenterAddNotificationEvent';
export * from './NotificationCenterAlertEvent';
export * from './NotificationCenterEvent';
export * from './SimpleAlertUIEvent';

View File

@ -26,4 +26,5 @@
@import './draggable-window/DraggableWindow';
@import './loading-spinner/LoadingSpinnerView';
@import './mini-camera/NitroLayoutMiniCameraView';
@import './notification-bubble/NotificationBubbleView';
@import './trophy/NitroLayoutTrophyView';

View File

@ -2,5 +2,6 @@ export * from './card';
export * from './draggable-window';
export * from './loading-spinner';
export * from './mini-camera';
export * from './notification-bubble';
export * from './transitions';
export * from './trophy';

View File

@ -0,0 +1,19 @@
.nitro-notification-bubble {
pointer-events: all;
background-color: $gable-green;
border: 2px solid rgba($white, 0.5);
font-size: $font-size-sm;
margin-bottom: 5px;
&.club-gift {
}
.bubble-image {
width: 30px;
height: 30px;
overflow: hidden;
object-fit: none;
margin-right: 5px;
}
}

View File

@ -0,0 +1,28 @@
import { FC, useEffect, useState } from 'react';
import { NotificationBubbleViewProps } from './NotificationBubbleView.types';
export const NotificationBubbleView: FC<NotificationBubbleViewProps> = props =>
{
const { fadesOut = false, close = null, className = '', children = null, ...rest } = props;
const [ isFading, setIsFading ] = useState(false);
useEffect(() =>
{
if(!fadesOut) return;
const timeout = setTimeout(() =>
{
setIsFading(true);
setTimeout(() => close());
}, 8000);
return () => clearTimeout(timeout);
}, [ fadesOut, close ]);
return (
<div className={ ('nitro-notification-bubble p-1 rounded ' + (className || '')) } { ...rest } onClick={ close }>
{ children }
</div>
)
}

View File

@ -0,0 +1,7 @@
import { DetailsHTMLAttributes } from 'react';
export interface NotificationBubbleViewProps extends DetailsHTMLAttributes<HTMLDivElement>
{
fadesOut?: boolean;
close: () => void;
}

View File

@ -0,0 +1,2 @@
export * from './NotificationBubbleView';
export * from './NotificationBubbleView.types';

View File

@ -0,0 +1,26 @@
export class CatalogPageName
{
public static DUCKET_INFO: string = 'ducket_info';
public static CREDITS: string = 'credits';
public static AVATAR_EFFECTS: string = 'avatar_effects';
public static HC_MEMBERSHIP: string = 'hc_membership';
public static CLUB_GIFTS: string = 'club_gifts';
public static LIMITED_SOLD: string = 'limited_sold';
public static PET_ACCESSORIES: string = 'pet_accessories';
public static TRAX_SONGS: string = 'trax_songs';
public static NEW_ADDITIONS: string = 'new_additions';
public static QUEST_SHELL: string = 'quest_shell';
public static QUEST_SNOWFLAKES: string = 'quest_snowflakes';
public static VAL_QUESTS: string = 'val_quests';
public static GUILD_CUSTOM_FURNI: string = 'guild_custom_furni';
public static GIFT_SHOP: string = 'gift_shop';
public static HORSE_STYLES: string = 'horse_styles';
public static HORSE_SHOE: string = 'horse_shoe';
public static SET_EASTER: string = 'set_easter';
public static ECOTRON_TRANSFORM: string = 'ecotron_transform';
public static LOYALTY_INFO: string = 'loyalty_info';
public static ROOM_BUNDLES: string = 'room_bundles';
public static ROOM_BUNDLES_MOBILE: string = 'room_bundles_mobile';
public static HABBO_CLUB_DESKTOP: string = 'habbo_club_desktop';
public static MOBILE_SUBSCRIPTIONS: string = 'mobile_subscriptions';
}

View File

@ -1,6 +1,6 @@
import { AchievementNotificationMessageEvent, HabboBroadcastMessageEvent, HotelWillShutdownEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, RespectReceivedEvent } from '@nitrots/nitro-renderer';
import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, HabboBroadcastMessageEvent, HotelClosesAndWillOpenAtEvent, HotelWillShutdownEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetAddedToInventoryEvent, RespectReceivedEvent, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { GetSessionDataManager, LocalizeBadgeName, LocalizeText } from '../../api';
import { GetRoomEngine, GetSessionDataManager, LocalizeBadgeName, LocalizeText } from '../../api';
import { NotificationCenterAlertEvent } from '../../events';
import { dispatchUiEvent } from '../../hooks/events';
import { CreateMessageHook } from '../../hooks/messages';
@ -15,13 +15,53 @@ export const NotificationCenterMessageHandler: FC<INotificationCenterMessageHand
{
const { dispatchNotificationCenterState = null } = useNotificationCenterContext();
const onRespectReceivedEvent = useCallback((event: RespectReceivedEvent) =>
{
const parser = event.getParser();
if(parser.userId !== GetSessionDataManager().userId) return;
const text1 = LocalizeText('notifications.text.respect.1');
const text2 = LocalizeText('notifications.text.respect.2', [ 'count' ], [ parser.respectsReceived.toString() ]);
NotificationUtilities.showSingleBubble(text1, NotificationType.RESPECT);
NotificationUtilities.showSingleBubble(text2, NotificationType.RESPECT);
}, []);
CreateMessageHook(RespectReceivedEvent, onRespectReceivedEvent);
const onHabboBroadcastMessageEvent = useCallback((event: HabboBroadcastMessageEvent) =>
{
const parser = event.getParser();
dispatchUiEvent(new NotificationCenterAlertEvent(NotificationCenterAlertEvent.HOTEL_ALERT, [ parser.message ]));
NotificationUtilities.simpleAlert(parser.message.replace(/\\r/g, '\r'));
}, []);
CreateMessageHook(HabboBroadcastMessageEvent, onHabboBroadcastMessageEvent);
const onAchievementNotificationMessageEvent = useCallback((event: AchievementNotificationMessageEvent) =>
{
const parser = event.getParser();
const text1 = LocalizeText('achievements.levelup.desc');
const badgeName = LocalizeBadgeName(parser.data.badgeCode);
const badgeImage = GetSessionDataManager().getBadgeUrl(parser.data.badgeCode);
const internalLink = 'questengine/achievements/' + parser.data.category;
NotificationUtilities.showSingleBubble((text1 + ' ' + badgeName), NotificationType.ACHIEVEMENT, badgeImage, internalLink);
}, []);
CreateMessageHook(AchievementNotificationMessageEvent, onAchievementNotificationMessageEvent);
const onClubGiftNotificationEvent = useCallback((event: ClubGiftNotificationEvent) =>
{
const parser = event.getParser();
NotificationUtilities.showClubGiftNotification(parser.numGifts);
}, []);
CreateMessageHook(ClubGiftNotificationEvent, onClubGiftNotificationEvent);
const onModeratorMessageEvent = useCallback((event: ModeratorMessageEvent) =>
{
const parser = event.getParser();
@ -29,6 +69,43 @@ export const NotificationCenterMessageHandler: FC<INotificationCenterMessageHand
dispatchUiEvent(new NotificationCenterAlertEvent(NotificationCenterAlertEvent.HOTEL_ALERT, [ parser.message ], parser.link));
}, []);
CreateMessageHook(ModeratorMessageEvent, onModeratorMessageEvent);
const onActivityPointNotificationMessageEvent = useCallback((event: ActivityPointNotificationMessageEvent) =>
{
const parser = event.getParser();
// bubble for loyalty
}, []);
CreateMessageHook(ActivityPointNotificationMessageEvent, onActivityPointNotificationMessageEvent);
const onHotelClosesAndWillOpenAtEvent = useCallback((event: HotelClosesAndWillOpenAtEvent) =>
{
const parser = event.getParser();
NotificationUtilities.handleHotelClosedMessage(parser.openHour, parser.openMinute, parser.userThrowOutAtClose);
}, []);
CreateMessageHook(HotelClosesAndWillOpenAtEvent, onHotelClosesAndWillOpenAtEvent);
const onPetAddedToInventoryEvent = useCallback((event: PetAddedToInventoryEvent) =>
{
const parser = event.getParser();
const text = LocalizeText('notifications.text.' + (parser.boughtAsGift ? 'petbought' : 'petreceived'));
let imageUrl: string = null;
const imageResult = GetRoomEngine().getRoomObjectPetImage(parser.pet.typeId, parser.pet.paletteId, parseInt(parser.pet.color, 16), new Vector3d(45 * 3), 64, null, true);
if(imageResult) imageUrl = imageResult.getImage().src;
NotificationUtilities.showSingleBubble(text, NotificationType.PETLEVEL, imageUrl);
}, []);
CreateMessageHook(PetAddedToInventoryEvent, onPetAddedToInventoryEvent);
const onMOTDNotificationEvent = useCallback((event: MOTDNotificationEvent) =>
{
const parser = event.getParser();
@ -55,38 +132,9 @@ export const NotificationCenterMessageHandler: FC<INotificationCenterMessageHand
NotificationUtilities.showNotification(parser.type, parser.parameters);
}, []);
const onRespectReceivedEvent = useCallback((event: RespectReceivedEvent) =>
{
const parser = event.getParser();
if(parser.userId !== GetSessionDataManager().userId) return;
const text1 = LocalizeText('notifications.text.respect.1');
const text2 = LocalizeText('notifications.text.respect.2', [ 'count' ], [ parser.respectsReceived.toString() ]);
NotificationUtilities.showSingleBubble(text1, NotificationType.RESPECT);
NotificationUtilities.showSingleBubble(text2, NotificationType.RESPECT);
}, []);
const onAchievementNotificationMessageEvent = useCallback((event: AchievementNotificationMessageEvent) =>
{
const parser = event.getParser();
const text1 = LocalizeText('achievements.levelup.desc');
const badgeName = LocalizeBadgeName(parser.data.badgeCode);
const badgeImage = GetSessionDataManager().getBadgeUrl(parser.data.badgeCode);
const internalLink = 'questengine/achievements/' + parser.data.category;
NotificationUtilities.showSingleBubble((text1 + ' ' + badgeName), NotificationType.ACHIEVEMENT, badgeImage, internalLink);
}, []);
CreateMessageHook(HabboBroadcastMessageEvent, onHabboBroadcastMessageEvent);
CreateMessageHook(ModeratorMessageEvent, onModeratorMessageEvent);
CreateMessageHook(MOTDNotificationEvent, onMOTDNotificationEvent);
CreateMessageHook(HotelWillShutdownEvent, onHotelWillShutdownEvent);
CreateMessageHook(NotificationDialogMessageEvent, onNotificationDialogMessageEvent);
CreateMessageHook(RespectReceivedEvent, onRespectReceivedEvent);
CreateMessageHook(AchievementNotificationMessageEvent, onAchievementNotificationMessageEvent);
return null;
}

View File

@ -7,18 +7,6 @@
}
}
.nitro-notification-center {
.notification-bubble {
pointer-events: all;
padding: 2px;
background-color: $gable-green;
border: 2px solid rgba($white, 0.5);
font-size: $font-size-sm;
margin-bottom: 5px;
}
}
.nitro-notification-center-container {
position: absolute;
top: 0;

View File

@ -3,10 +3,11 @@ import { NotificationCenterAlertEvent } from '../../events';
import { NotificationBubbleEvent } from '../../events/notification-center/NotificationBubbleEvent';
import { useUiEvent } from '../../hooks/events';
import { NotificationItem } from './common/NotificationItem';
import { NotificationType } from './common/NotificationType';
import { NotificationCenterMessageHandler } from './NotificationCenterMessageHandler';
import { NotificationCenterViewProps } from './NotificationCenterView.types';
import { NotificationCenterBroadcastMessageView } from './views/broadcast-message/NotificationCenterBroadcastMessageView';
import { NotificationBubbleView } from './views/notification-bubble/NotificationBubbleView';
import { GetBubbleLayout } from './views/bubble-layouts/GetBubbleLayout';
export const NotificationCenterView: FC<NotificationCenterViewProps> = props =>
{
@ -26,7 +27,7 @@ export const NotificationCenterView: FC<NotificationCenterViewProps> = props =>
const onNotificationBubbleEvent = useCallback((event: NotificationBubbleEvent) =>
{
console.log(event);
const notificationItem = new NotificationItem(event.message, event.notificationType, null, event.linkUrl);
const notificationItem = new NotificationItem(event.message, event.notificationType, event.imageUrl, event.linkUrl);
setBubbleAlerts(prevValue => [ notificationItem, ...prevValue ]);
}, []);
@ -65,7 +66,19 @@ export const NotificationCenterView: FC<NotificationCenterViewProps> = props =>
const elements: ReactNode[] = [];
for(const alert of bubbleAlerts) elements.push(<NotificationBubbleView key={ alert.id } notificationItem={ alert } close={ () => closeBubbleAlert(alert) } />);
for(const alert of bubbleAlerts)
{
const element = GetBubbleLayout(alert, () => closeBubbleAlert(alert));
if(alert.notificationType === NotificationType.CLUBGIFT)
{
elements.unshift(element);
continue;
}
elements.push(element);
}
return elements;
}, [ bubbleAlerts, closeBubbleAlert ]);

View File

@ -1,10 +1,25 @@
import { GetConfiguration, GetNitroInstance, LocalizeText } from '../../../api';
import { HabboWebTools } from '@nitrots/nitro-renderer';
import { CreateLinkEvent, GetConfiguration, GetNitroInstance, LocalizeText } from '../../../api';
import { SimpleAlertUIEvent } from '../../../events';
import { NotificationBubbleEvent } from '../../../events/notification-center/NotificationBubbleEvent';
import { dispatchUiEvent } from '../../../hooks';
import { CatalogPageName } from '../../catalog/common/CatalogPageName';
import { NotificationType } from './NotificationType';
export class NotificationUtilities
{
private static cleanText(text: string): string
{
return text.replace(/\\r/g, '\r')
}
private static getTimeZeroPadded(time: number): string
{
const text = ('0' + time);
return text.substr((text.length - 2), text.length);
}
private static getMainNotificationConfig(): { [key: string]: { delivery?: string, display?: string; title?: string; image?: string }}
{
return GetConfiguration<{ [key: string]: { delivery?: string, display?: string; title?: string; image?: string }}>('notification', {});
@ -63,7 +78,7 @@ export class NotificationUtilities
const isEventLink = (linkUrl && linkUrl.substr(0, 6) === 'event');
const image = this.getNotificationImageUrl(options, type);
dispatchUiEvent(new NotificationBubbleEvent(message, NotificationType.INFO, image, (isEventLink ? linkUrl.substr(6) : linkUrl)));
dispatchUiEvent(new NotificationBubbleEvent(LocalizeText(message), NotificationType.INFO, LocalizeText(image), (isEventLink ? linkUrl.substr(6) : linkUrl)));
}
else
{
@ -75,4 +90,47 @@ export class NotificationUtilities
{
dispatchUiEvent(new NotificationBubbleEvent(message, type, imageUrl, internalLink));
}
public static simpleAlert(message: string, clickUrl: string = null, clickUrlText: string = null, title: string = null, imageUrl: string = null): void
{
if(!title || !title.length) title = LocalizeText('notifications.broadcast.title');
dispatchUiEvent(new SimpleAlertUIEvent(message, clickUrl, clickUrlText, title, imageUrl));
}
public static alert(title: string, message: string): void
{
dispatchUiEvent(new SimpleAlertUIEvent(message, null, null, title, null));
}
public static showClubGiftNotification(numGifts: number): void
{
if(numGifts <= 0) return;
dispatchUiEvent(new NotificationBubbleEvent(numGifts.toString(), NotificationType.CLUBGIFT, null, 'catalog/open/' + CatalogPageName.CLUB_GIFTS));
}
public static showModeratorMessage(message: string, url: string = null): void
{
this.simpleAlert(this.cleanText(message), url, LocalizeText('mod.alert.link'), LocalizeText('mod.alert.title'));
}
public static handleHotelClosedMessage(open: number, minute: number, thrownOut: boolean): void
{
const text: string = LocalizeText(('opening.hours.' + (thrownOut ? 'disconnected' : 'closed')), [ 'h', 'm'], [ this.getTimeZeroPadded(open), this.getTimeZeroPadded(minute) ]);;
this.alert(LocalizeText('opening.hours.title'), text);
}
public static openUrl(url: string): void
{
if(url.startsWith('http'))
{
HabboWebTools.openWebPage(url);
}
else
{
CreateLinkEvent(url);
}
}
}

View File

@ -0,0 +1,19 @@
import { NotificationItem } from '../../common/NotificationItem';
import { NotificationType } from '../../common/NotificationType';
import { NotificationClubGiftBubbleView } from './club-gift/NotificationClubGiftBubbleView';
import { NotificationDefaultBubbleView } from './default/NotificationDefaultBubbleView';
export const GetBubbleLayout = (item: NotificationItem, close: () => void) =>
{
if(!item) return null;
const props = { key: item.id, item, close };
switch(item.notificationType)
{
case NotificationType.CLUBGIFT:
return <NotificationClubGiftBubbleView { ...props } />
default:
return <NotificationDefaultBubbleView { ...props } />
}
}

View File

@ -1,7 +1,7 @@
import { NotificationItem } from '../../common/NotificationItem';
export interface NotificationBubbleViewProps
export interface NotificationBubbleLayoutViewProps
{
notificationItem: NotificationItem;
item: NotificationItem;
close: () => void;
}

View File

@ -0,0 +1,24 @@
import { FC } from 'react';
import { LocalizeText } from '../../../../../api';
import { NotificationBubbleView } from '../../../../../layout';
import { CurrencyIcon } from '../../../../shared/currency-icon/CurrencyIcon';
import { NotificationUtilities } from '../../../common/NotificationUtilities';
import { NotificationBubbleLayoutViewProps } from '../NotificationBubbleLayoutView.types';
export const NotificationClubGiftBubbleView: FC<NotificationBubbleLayoutViewProps> = props =>
{
const { item = null, close = null, ...rest } = props;
return (
<NotificationBubbleView className="flex-column club-gift" close={ close } { ...rest }>
<div className="d-flex mb-1">
<CurrencyIcon type="hc" />
<span className="ms-1">{ LocalizeText('notifications.text.club_gift') }</span>
</div>
<div className="d-flex align-items-center justify-content-end">
<span className="fw-bold me-1" onClick={ close }>{ LocalizeText('notifications.button.later') }</span>
<button type="button" className="btn btn-primary btn-sm" onClick={ () => NotificationUtilities.openUrl(item.linkUrl) }>{ LocalizeText('notifications.button.show_gift_list') }</button>
</div>
</NotificationBubbleView>
);
}

View File

@ -0,0 +1,15 @@
import { FC } from 'react';
import { NotificationBubbleView } from '../../../../../layout';
import { NotificationDefaultBubbleViewProps } from './NotificationDefaultBubbleView.types';
export const NotificationDefaultBubbleView: FC<NotificationDefaultBubbleViewProps> = props =>
{
const { item = null, close = null, ...rest } = props;
return (
<NotificationBubbleView className="d-flex" fadesOut={ false } close={ close } { ...rest }>
{ (item.iconUrl && item.iconUrl.length) && <img className="bubble-image no-select" src={ item.iconUrl } alt="" /> }
<span>{ item.message }</span>
</NotificationBubbleView>
);
}

View File

@ -0,0 +1,7 @@
import { DetailsHTMLAttributes } from 'react';
import { NotificationBubbleLayoutViewProps } from '../NotificationBubbleLayoutView.types';
export interface NotificationDefaultBubbleViewProps extends NotificationBubbleLayoutViewProps, DetailsHTMLAttributes<HTMLDivElement>
{
}

View File

@ -1,19 +0,0 @@
import { FC, useMemo } from 'react';
import { LocalizeText } from '../../../../api';
import { NotificationBubbleViewProps } from './NotificationBubbleView.types';
export const NotificationBubbleView: FC<NotificationBubbleViewProps> = props =>
{
const { notificationItem = null, close = null } = props;
const message = useMemo(() =>
{
return LocalizeText(notificationItem.message);
}, [ notificationItem ]);
return (
<div className="notification-bubble">
{ message }
</div>
);
}