From 27d7083440a3338c143b9d50ab64c0f71680a7d6 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Aug 2021 01:00:05 -0400 Subject: [PATCH] Notification center updates --- .../NotificationBubbleEvent.ts | 41 ++++++++++ src/views/main/MainView.tsx | 2 - .../NotificationCenterMessageHandler.tsx | 42 +++++++--- .../NotificationCenterView.scss | 12 +++ .../NotificationCenterView.tsx | 45 ++++++++++- .../common/NotificationItem.ts | 48 ++++++++++++ .../common/NotificationType.ts | 19 +++++ .../common/NotificationUtilities.ts | 78 +++++++++++++++++++ .../NotificationBubbleView.tsx | 16 +++- .../NotificationBubbleView.types.ts | 5 +- src/views/right-side/RightSideView.scss | 3 +- src/views/right-side/RightSideView.tsx | 4 +- 12 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 src/events/notification-center/NotificationBubbleEvent.ts create mode 100644 src/views/notification-center/common/NotificationItem.ts create mode 100644 src/views/notification-center/common/NotificationType.ts create mode 100644 src/views/notification-center/common/NotificationUtilities.ts diff --git a/src/events/notification-center/NotificationBubbleEvent.ts b/src/events/notification-center/NotificationBubbleEvent.ts new file mode 100644 index 00000000..11a343a2 --- /dev/null +++ b/src/events/notification-center/NotificationBubbleEvent.ts @@ -0,0 +1,41 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class NotificationBubbleEvent extends NitroEvent +{ + public static NEW_BUBBLE: string = 'NNBE_NEW_BUBBLE'; + + private _message: string; + private _notificationType: string; + private _imageUrl: string; + private _linkUrl: string; + + constructor(message: string, notificationType: string, imageUrl: string, linkUrl: string) + { + super(NotificationBubbleEvent.NEW_BUBBLE); + + this._message = message; + this._notificationType = notificationType; + this._imageUrl = imageUrl; + this._linkUrl = linkUrl; + } + + public get message(): string + { + return this._message; + } + + public get notificationType(): string + { + return this._notificationType; + } + + public get imageUrl(): string + { + return this._imageUrl; + } + + public get linkUrl(): string + { + return this._linkUrl; + } +} diff --git a/src/views/main/MainView.tsx b/src/views/main/MainView.tsx index bcf070d5..657f778c 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -10,7 +10,6 @@ import { HotelView } from '../hotel-view/HotelView'; import { InventoryView } from '../inventory/InventoryView'; import { ModToolsView } from '../mod-tools/ModToolsView'; import { NavigatorView } from '../navigator/NavigatorView'; -import { NotificationCenterView } from '../notification-center/NotificationCenterView'; import { RightSideView } from '../right-side/RightSideView'; import { RoomHostView } from '../room-host/RoomHostView'; import { ToolbarView } from '../toolbar/ToolbarView'; @@ -61,7 +60,6 @@ export const MainView: FC = props => - diff --git a/src/views/notification-center/NotificationCenterMessageHandler.tsx b/src/views/notification-center/NotificationCenterMessageHandler.tsx index c0e06986..831f0198 100644 --- a/src/views/notification-center/NotificationCenterMessageHandler.tsx +++ b/src/views/notification-center/NotificationCenterMessageHandler.tsx @@ -1,10 +1,12 @@ -import { HabboBroadcastMessageEvent, HotelWillShutdownEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent } from '@nitrots/nitro-renderer'; +import { AchievementNotificationMessageEvent, HabboBroadcastMessageEvent, HotelWillShutdownEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, RespectReceivedEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; +import { GetSessionDataManager, LocalizeBadgeName, LocalizeText } from '../../api'; import { NotificationCenterAlertEvent } from '../../events'; import { dispatchUiEvent } from '../../hooks/events'; import { CreateMessageHook } from '../../hooks/messages'; -import { DialogMessageNotification } from './common/DialogMessageNotification'; import { HotelWillShutdownNotification } from './common/HotelWillShutdownNotification'; +import { NotificationType } from './common/NotificationType'; +import { NotificationUtilities } from './common/NotificationUtilities'; import { useNotificationCenterContext } from './context/NotificationCenterContext'; import { INotificationCenterMessageHandlerProps } from './NotificationCenterMessageHandler.types'; import { NotificationCenterActions } from './reducers/NotificationCenterReducer'; @@ -50,21 +52,41 @@ export const NotificationCenterMessageHandler: FC + { + 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; } diff --git a/src/views/notification-center/NotificationCenterView.scss b/src/views/notification-center/NotificationCenterView.scss index 78a94d63..0c61bdd2 100644 --- a/src/views/notification-center/NotificationCenterView.scss +++ b/src/views/notification-center/NotificationCenterView.scss @@ -7,6 +7,18 @@ } } +.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; diff --git a/src/views/notification-center/NotificationCenterView.tsx b/src/views/notification-center/NotificationCenterView.tsx index c5df02ea..91336c7e 100644 --- a/src/views/notification-center/NotificationCenterView.tsx +++ b/src/views/notification-center/NotificationCenterView.tsx @@ -1,15 +1,17 @@ -import { FC, useCallback, useState } from 'react'; +import { FC, ReactNode, useCallback, useMemo, useState } from 'react'; import { NotificationCenterAlertEvent } from '../../events'; +import { NotificationBubbleEvent } from '../../events/notification-center/NotificationBubbleEvent'; import { useUiEvent } from '../../hooks/events'; +import { NotificationItem } from './common/NotificationItem'; import { NotificationCenterMessageHandler } from './NotificationCenterMessageHandler'; import { NotificationCenterViewProps } from './NotificationCenterView.types'; import { NotificationCenterBroadcastMessageView } from './views/broadcast-message/NotificationCenterBroadcastMessageView'; +import { NotificationBubbleView } from './views/notification-bubble/NotificationBubbleView'; export const NotificationCenterView: FC = props => { const [ alerts, setAlerts ] = useState([]); - - + const [ bubbleAlerts, setBubbleAlerts ] = useState([]); const onNotificationCenterAlertEvent = useCallback((event: NotificationCenterAlertEvent) => { @@ -21,6 +23,16 @@ export const NotificationCenterView: FC = props => useUiEvent(NotificationCenterAlertEvent.HOTEL_ALERT, onNotificationCenterAlertEvent); + const onNotificationBubbleEvent = useCallback((event: NotificationBubbleEvent) => + { + console.log(event); + const notificationItem = new NotificationItem(event.message, event.notificationType, null, event.linkUrl); + + setBubbleAlerts(prevValue => [ notificationItem, ...prevValue ]); + }, []); + + useUiEvent(NotificationBubbleEvent.NEW_BUBBLE, onNotificationBubbleEvent); + const closeAlert = useCallback((alert: NotificationCenterAlertEvent) => { setAlerts(prevValue => @@ -34,9 +46,36 @@ export const NotificationCenterView: FC = props => }); }, []); + const closeBubbleAlert = useCallback((item: NotificationItem) => + { + setBubbleAlerts(prevValue => + { + const newAlerts = [ ...prevValue ]; + const index = newAlerts.findIndex(value => (item === value)); + + if(index >= 0) newAlerts.splice(index, 1); + + return newAlerts; + }) + }, []); + + const getBubbleAlerts = useMemo(() => + { + if(!bubbleAlerts || !bubbleAlerts.length) return null; + + const elements: ReactNode[] = []; + + for(const alert of bubbleAlerts) elements.push( closeBubbleAlert(alert) } />); + + return elements; + }, [ bubbleAlerts, closeBubbleAlert ]); + return ( <> +
+ { getBubbleAlerts } +
{ (alerts.length > 0) && alerts.map((alert, index) => { switch(alert.type) diff --git a/src/views/notification-center/common/NotificationItem.ts b/src/views/notification-center/common/NotificationItem.ts new file mode 100644 index 00000000..9387db74 --- /dev/null +++ b/src/views/notification-center/common/NotificationItem.ts @@ -0,0 +1,48 @@ +import { NotificationType } from './NotificationType'; + +export class NotificationItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _message: string; + private _notificationType: string; + private _iconUrl: string; + private _linkUrl: string; + + constructor(message: string, notificationType: string = NotificationType.INFO, iconUrl: string = null, linkUrl: string = null) + { + NotificationItem.ITEM_ID += 1; + + this._id = NotificationItem.ITEM_ID; + this._message = message; + this._notificationType = notificationType; + this._iconUrl = iconUrl; + this._linkUrl = linkUrl; + } + + public get id(): number + { + return this._id; + } + + public get message(): string + { + return this._message; + } + + public get notificationType(): string + { + return this._notificationType; + } + + public get iconUrl(): string + { + return this._iconUrl; + } + + public get linkUrl(): string + { + return this._linkUrl; + } +} diff --git a/src/views/notification-center/common/NotificationType.ts b/src/views/notification-center/common/NotificationType.ts new file mode 100644 index 00000000..dff016d8 --- /dev/null +++ b/src/views/notification-center/common/NotificationType.ts @@ -0,0 +1,19 @@ +export class NotificationType +{ + public static FRIENDOFFLINE: string = 'friendoffline'; + public static FRIENDONLINE: string = 'friendonline'; + public static THIRDPARTYFRIENDOFFLINE: string = 'thirdpartyfriendoffline'; + public static THIRDPARTYFRIENDONLINE: string = 'thirdpartyfriendonline'; + public static ACHIEVEMENT: string = 'achievement'; + public static BADGE_RECEIVED: string = 'badge_received'; + public static INFO: string = 'info'; + public static RECYCLEROK: string = 'recyclerok'; + public static RESPECT: string = 'respect'; + public static CLUB: string = 'club'; + public static SOUNDMACHINE: string = 'soundmachine'; + public static PETLEVEL: string = 'petlevel'; + public static CLUBGIFT: string = 'clubgift'; + public static BUYFURNI: string = 'buyfurni'; + public static VIP: string = 'vip'; + public static ROOMMESSAGESPOSTED: string = 'roommessagesposted'; +} diff --git a/src/views/notification-center/common/NotificationUtilities.ts b/src/views/notification-center/common/NotificationUtilities.ts new file mode 100644 index 00000000..ba8ded58 --- /dev/null +++ b/src/views/notification-center/common/NotificationUtilities.ts @@ -0,0 +1,78 @@ +import { GetConfiguration, GetNitroInstance, LocalizeText } from '../../../api'; +import { NotificationBubbleEvent } from '../../../events/notification-center/NotificationBubbleEvent'; +import { dispatchUiEvent } from '../../../hooks'; +import { NotificationType } from './NotificationType'; + +export class NotificationUtilities +{ + 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', {}); + } + + private static getNotificationConfig(key: string): { delivery?: string, display?: string; title?: string; image?: string } + { + const mainConfig = this.getMainNotificationConfig(); + + if(!mainConfig) return null; + + return mainConfig[key]; + } + + public static getNotificationPart(options: Map, type: string, key: string, localize: boolean): string + { + if(options.has(key)) return options.get(key); + + const localizeKey = [ 'notification', type, key ].join('.'); + + if(GetNitroInstance().localization.hasValue(localizeKey) || localize) + { + return LocalizeText(localizeKey, Array.from(options.keys()), Array.from(options.values())); + } + + return null; + } + + public static getNotificationImageUrl(options: Map, type: string): string + { + let imageUrl = options.get('image'); + + // eslint-disable-next-line no-template-curly-in-string + if(!imageUrl) imageUrl = ('${image.library.url}notifications/' + type.replace(/\./g, '_') + '.png'); + + return imageUrl; + } + + public static showNotification(type: string, options: Map = null): void + { + if(!options) options = new Map(); + + const configuration = this.getNotificationConfig(('notification.' + type)); + + if(configuration) + { + for(const key in configuration) options.set(key, configuration[key]); + } + + console.log(options); + + if(options.get('display') === 'BUBBLE') + { + const message = this.getNotificationPart(options, type, 'message', true); + const linkUrl = this.getNotificationPart(options, type, 'linkUrl', false); + 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))); + } + else + { + + } + } + + public static showSingleBubble(message: string, type: string, imageUrl: string = null, internalLink: string = null): void + { + dispatchUiEvent(new NotificationBubbleEvent(message, type, imageUrl, internalLink)); + } +} diff --git a/src/views/notification-center/views/notification-bubble/NotificationBubbleView.tsx b/src/views/notification-center/views/notification-bubble/NotificationBubbleView.tsx index 1803bae9..42efec99 100644 --- a/src/views/notification-center/views/notification-bubble/NotificationBubbleView.tsx +++ b/src/views/notification-center/views/notification-bubble/NotificationBubbleView.tsx @@ -1,7 +1,19 @@ -import { FC } from 'react'; +import { FC, useMemo } from 'react'; +import { LocalizeText } from '../../../../api'; import { NotificationBubbleViewProps } from './NotificationBubbleView.types'; export const NotificationBubbleView: FC = props => { - return null; + const { notificationItem = null, close = null } = props; + + const message = useMemo(() => + { + return LocalizeText(notificationItem.message); + }, [ notificationItem ]); + + return ( +
+ { message } +
+ ); } diff --git a/src/views/notification-center/views/notification-bubble/NotificationBubbleView.types.ts b/src/views/notification-center/views/notification-bubble/NotificationBubbleView.types.ts index f46fd92a..71020810 100644 --- a/src/views/notification-center/views/notification-bubble/NotificationBubbleView.types.ts +++ b/src/views/notification-center/views/notification-bubble/NotificationBubbleView.types.ts @@ -1,4 +1,7 @@ +import { NotificationItem } from '../../common/NotificationItem'; + export interface NotificationBubbleViewProps { - + notificationItem: NotificationItem; + close: () => void; } diff --git a/src/views/right-side/RightSideView.scss b/src/views/right-side/RightSideView.scss index fd2c00c3..a5977419 100644 --- a/src/views/right-side/RightSideView.scss +++ b/src/views/right-side/RightSideView.scss @@ -3,7 +3,8 @@ top: 0px; right: 10px; min-width: 200px; - max-width: 400px; + max-width: 200px; + height: calc(100% - #{$toolbar-height}); z-index: $rightside-zindex; pointer-events: none; } diff --git a/src/views/right-side/RightSideView.tsx b/src/views/right-side/RightSideView.tsx index ba025225..5917174e 100644 --- a/src/views/right-side/RightSideView.tsx +++ b/src/views/right-side/RightSideView.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { NotificationCenterView } from '../notification-center/NotificationCenterView'; import { PurseView } from '../purse/PurseView'; import { RightSideProps } from './RightSideView.types'; @@ -6,8 +7,9 @@ export const RightSideView: FC = props => { return (
-
+
+
);