mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
feat(mentions): inbox window, toolbar badge, chat-history tab, ui-config + i18n
This commit is contained in:
@@ -772,4 +772,13 @@
|
|||||||
'usersettings.success.password': "Password aggiornata con successo.",
|
'usersettings.success.password': "Password aggiornata con successo.",
|
||||||
'usersettings.success.email': "Email aggiornata con successo.",
|
'usersettings.success.email': "Email aggiornata con successo.",
|
||||||
'usersettings.success.username': "Nome utente aggiornato. Accedi di nuovo con il tuo nuovo nome.",
|
'usersettings.success.username': "Nome utente aggiornato. Accedi di nuovo con il tuo nuovo nome.",
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Mentions
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
'mentions.window.title': 'Menzioni',
|
||||||
|
'mentions.window.empty': 'Nessuna menzione',
|
||||||
|
'mentions.window.markall': 'Segna tutte come lette',
|
||||||
|
'mentions.tab.title': 'Menzioni',
|
||||||
|
'mentions.notification': '%sender% ti ha menzionato in %room%',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
"wired.action.mute.user.max.length": 100,
|
"wired.action.mute.user.max.length": 100,
|
||||||
"game.center.enabled": false,
|
"game.center.enabled": false,
|
||||||
"radio_ui.enabled": false,
|
"radio_ui.enabled": false,
|
||||||
|
"mentions_ui.enabled": true,
|
||||||
|
"mentions_ui.sound": true,
|
||||||
"guides.enabled": true,
|
"guides.enabled": true,
|
||||||
"housekeeping.enabled": true,
|
"housekeeping.enabled": true,
|
||||||
"toolbar.hide.quests": true,
|
"toolbar.hide.quests": true,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { AddLinkEventTracker, GetCommunication, GetRoomSessionManager, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, GetCommunication, GetRoomSessionManager, HabboWebTools, ILinkEventTracker, MarkMentionsReadComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { GetConfigurationValue } from '../api';
|
import { GetConfigurationValue, SendMessageComposer } from '../api';
|
||||||
import { useNitroEventReducer } from '../hooks';
|
import { useMentionMessages, useNitroEventReducer } from '../hooks';
|
||||||
|
import { markAllRead } from '../hooks/mentions/mentionsStore';
|
||||||
import { AchievementsView } from './achievements/AchievementsView';
|
import { AchievementsView } from './achievements/AchievementsView';
|
||||||
import { AvatarEditorView } from './avatar-editor';
|
import { AvatarEditorView } from './avatar-editor';
|
||||||
import { BadgeCreatorView } from './badge-creator';
|
import { BadgeCreatorView } from './badge-creator';
|
||||||
@@ -47,11 +48,15 @@ import { UserAccountSettingsView } from './user-settings/UserAccountSettingsView
|
|||||||
import { UserSettingsView } from './user-settings/UserSettingsView';
|
import { UserSettingsView } from './user-settings/UserSettingsView';
|
||||||
import { WiredView } from './wired/WiredView';
|
import { WiredView } from './wired/WiredView';
|
||||||
import { WiredCreatorToolsView } from './wired-tools/WiredCreatorToolsView';
|
import { WiredCreatorToolsView } from './wired-tools/WiredCreatorToolsView';
|
||||||
|
import { MentionsView } from './mentions';
|
||||||
|
|
||||||
export const MainView: FC<{}> = props =>
|
export const MainView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [ isReady, setIsReady ] = useState(false);
|
const [ isReady, setIsReady ] = useState(false);
|
||||||
const [ localizationVersion, setLocalizationVersion ] = useState(0);
|
const [ localizationVersion, setLocalizationVersion ] = useState(0);
|
||||||
|
const [ mentionsVisible, setMentionsVisible ] = useState(false);
|
||||||
|
|
||||||
|
useMentionMessages();
|
||||||
|
|
||||||
// CREATED and ENDED can arrive out of order under flaky reconnects.
|
// CREATED and ENDED can arrive out of order under flaky reconnects.
|
||||||
// Treating them as two independent setters left landingViewVisible
|
// Treating them as two independent setters left landingViewVisible
|
||||||
@@ -124,6 +129,54 @@ export const MainView: FC<{}> = props =>
|
|||||||
return () => RemoveLinkEventTracker(linkTracker);
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
// Opening the inbox clears the unread badge both locally and
|
||||||
|
// server-side so the toolbar count resets immediately.
|
||||||
|
const clearMentionsBadge = () =>
|
||||||
|
{
|
||||||
|
markAllRead();
|
||||||
|
SendMessageComposer(new MarkMentionsReadComposer(0, 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkTracker: ILinkEventTracker = {
|
||||||
|
linkReceived: (url: string) =>
|
||||||
|
{
|
||||||
|
const parts = url.split('/');
|
||||||
|
|
||||||
|
if(parts.length < 2) return;
|
||||||
|
|
||||||
|
switch(parts[1])
|
||||||
|
{
|
||||||
|
case 'show':
|
||||||
|
setMentionsVisible(true);
|
||||||
|
clearMentionsBadge();
|
||||||
|
return;
|
||||||
|
case 'hide':
|
||||||
|
setMentionsVisible(false);
|
||||||
|
return;
|
||||||
|
case 'toggle':
|
||||||
|
setMentionsVisible(prevValue =>
|
||||||
|
{
|
||||||
|
if(prevValue) return false;
|
||||||
|
|
||||||
|
// Side-effect-free in the updater: defer the
|
||||||
|
// badge-clear to a microtask so React's
|
||||||
|
// double-invoke (StrictMode) can't fire it twice.
|
||||||
|
queueMicrotask(clearMentionsBadge);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
eventUrlPrefix: 'mentions/'
|
||||||
|
};
|
||||||
|
|
||||||
|
AddLinkEventTracker(linkTracker);
|
||||||
|
|
||||||
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const refreshLocalization = () => setLocalizationVersion(value => (value + 1));
|
const refreshLocalization = () => setLocalizationVersion(value => (value + 1));
|
||||||
@@ -187,6 +240,8 @@ export const MainView: FC<{}> = props =>
|
|||||||
<FortuneWheelView />
|
<FortuneWheelView />
|
||||||
<SoundboardView />
|
<SoundboardView />
|
||||||
{ GetConfigurationValue<boolean>('radio_ui.enabled', false) && <RadioView /> }
|
{ GetConfigurationValue<boolean>('radio_ui.enabled', false) && <RadioView /> }
|
||||||
|
{ (GetConfigurationValue<boolean>('mentions_ui.enabled', true) && mentionsVisible) &&
|
||||||
|
<MentionsView onClose={ () => setMentionsVisible(false) } /> }
|
||||||
<ExternalPluginLoader />
|
<ExternalPluginLoader />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ChatEntryType, LocalizeText } from '../../api';
|
import { ChatEntryType, LocalizeText } from '../../api';
|
||||||
import { Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
import { Flex, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
|
||||||
import { useChatHistory, useOnClickChat } from '../../hooks';
|
import { useChatHistory, useMentionsSnapshot, useOnClickChat } from '../../hooks';
|
||||||
import { NitroInput } from '../../layout';
|
import { NitroInput } from '../../layout';
|
||||||
|
import { MentionRowView, useMentionRowClick } from '../mentions';
|
||||||
|
|
||||||
|
const TAB_CHAT = 'chat';
|
||||||
|
const TAB_MENTIONS = 'mentions';
|
||||||
|
|
||||||
export const ChatHistoryView: FC<{}> = props =>
|
export const ChatHistoryView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
const [activeTab, setActiveTab] = useState<string>(TAB_CHAT);
|
||||||
const { chatHistory = [] } = useChatHistory();
|
const { chatHistory = [] } = useChatHistory();
|
||||||
|
const { mentions } = useMentionsSnapshot();
|
||||||
|
const onMentionRowClick = useMentionRowClick();
|
||||||
const { onClickChat } = useOnClickChat();
|
const { onClickChat } = useOnClickChat();
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
const isFirstRender = useRef(true);
|
const isFirstRender = useRef(true);
|
||||||
@@ -87,7 +94,25 @@ export const ChatHistoryView: FC<{}> = props =>
|
|||||||
return (
|
return (
|
||||||
<NitroCardView className="w-[400px] h-[400px] bg-[#f0f0f0]" theme="primary-slim" uniqueKey="chat-history">
|
<NitroCardView className="w-[400px] h-[400px] bg-[#f0f0f0]" theme="primary-slim" uniqueKey="chat-history">
|
||||||
<NitroCardHeaderView headerText={LocalizeText('room.chathistory.button.text')} onCloseClick={event => setIsVisible(false)} />
|
<NitroCardHeaderView headerText={LocalizeText('room.chathistory.button.text')} onCloseClick={event => setIsVisible(false)} />
|
||||||
|
<NitroCardTabsView>
|
||||||
|
<NitroCardTabsItemView isActive={ activeTab === TAB_CHAT } onClick={ () => setActiveTab(TAB_CHAT) }>
|
||||||
|
{ LocalizeText('room.chathistory.button.text') }
|
||||||
|
</NitroCardTabsItemView>
|
||||||
|
<NitroCardTabsItemView count={ mentions.reduce((n, m) => n + (m.read ? 0 : 1), 0) } isActive={ activeTab === TAB_MENTIONS } onClick={ () => setActiveTab(TAB_MENTIONS) }>
|
||||||
|
{ LocalizeText('mentions.tab.title') }
|
||||||
|
</NitroCardTabsItemView>
|
||||||
|
</NitroCardTabsView>
|
||||||
<NitroCardContentView className="h-full bg-[#f0f0f0] bg-[url('@/assets/images/chat/chathistory_background.png')] bg-repeat bg-auto" gap={2} overflow="hidden" style={{ height: 'calc(100% - 40px)', display: 'flex', flexDirection: 'column' }}>
|
<NitroCardContentView className="h-full bg-[#f0f0f0] bg-[url('@/assets/images/chat/chathistory_background.png')] bg-repeat bg-auto" gap={2} overflow="hidden" style={{ height: 'calc(100% - 40px)', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
{ activeTab === TAB_MENTIONS ? (
|
||||||
|
<div style={{ flex: 1, overflowY: 'auto', background: 'inherit' }}>
|
||||||
|
{ (mentions.length === 0)
|
||||||
|
? <Text center variant="gray">{ LocalizeText('mentions.window.empty') }</Text>
|
||||||
|
: mentions.map(mention => (
|
||||||
|
<MentionRowView key={ mention.mentionId } mention={ mention } onClick={ onMentionRowClick } />
|
||||||
|
)) }
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<NitroInput placeholder={LocalizeText('generic.search')} type="text" value={searchText} onChange={event => setSearchText(event.target.value)} />
|
<NitroInput placeholder={LocalizeText('generic.search')} type="text" value={searchText} onChange={event => setSearchText(event.target.value)} />
|
||||||
<div ref={elementRef} style={{ flex: 1, overflowY: 'auto', background: 'inherit' }}>
|
<div ref={elementRef} style={{ flex: 1, overflowY: 'auto', background: 'inherit' }}>
|
||||||
{filteredChatHistory.map((row, index) => (
|
{filteredChatHistory.map((row, index) => (
|
||||||
@@ -119,6 +144,8 @@ export const ChatHistoryView: FC<{}> = props =>
|
|||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) }
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { IMentionEntry } from '../../api';
|
||||||
|
import { Flex, Text } from '../../common';
|
||||||
|
|
||||||
|
interface MentionRowViewProps
|
||||||
|
{
|
||||||
|
mention: IMentionEntry;
|
||||||
|
onClick: (mention: IMentionEntry) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MentionRowView: FC<MentionRowViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { mention, onClick } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex pointer alignItems="center" className="p-1 hover:bg-black/5" gap={ 2 } onClick={ () => onClick(mention) }>
|
||||||
|
<span
|
||||||
|
className={ `inline-block w-[8px] h-[8px] rounded-full shrink-0 ${ mention.read ? 'bg-transparent' : 'bg-[#1e7295]' }` } />
|
||||||
|
<Flex grow column className="min-w-0" gap={ 0 }>
|
||||||
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
|
<Text bold={ !mention.read } truncate variant="primary">{ mention.senderUsername }</Text>
|
||||||
|
{ (mention.roomName && mention.roomName.length > 0) &&
|
||||||
|
<Text small truncate variant="gray">{ mention.roomName }</Text> }
|
||||||
|
</Flex>
|
||||||
|
<Text truncate variant="black">{ mention.message }</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { MarkMentionsReadComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { LocalizeText, SendMessageComposer } from '../../api';
|
||||||
|
import { Button, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||||
|
import { useMentionsSnapshot } from '../../hooks';
|
||||||
|
import { markAllRead } from '../../hooks/mentions/mentionsStore';
|
||||||
|
import { MentionRowView } from './MentionRowView';
|
||||||
|
import { useMentionRowClick } from './useMentionRowClick';
|
||||||
|
|
||||||
|
interface MentionsViewProps
|
||||||
|
{
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MentionsView: FC<MentionsViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { onClose } = props;
|
||||||
|
const { mentions } = useMentionsSnapshot();
|
||||||
|
const onRowClick = useMentionRowClick();
|
||||||
|
|
||||||
|
const onMarkAll = useCallback(() =>
|
||||||
|
{
|
||||||
|
markAllRead();
|
||||||
|
SendMessageComposer(new MarkMentionsReadComposer(0, 0));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="w-[340px] h-[420px]" theme="primary-slim" uniqueKey="mentions">
|
||||||
|
<NitroCardHeaderView headerText={ LocalizeText('mentions.window.title') } onCloseClick={ onClose } />
|
||||||
|
<NitroCardContentView gap={ 1 }>
|
||||||
|
<Flex grow column className="min-h-0 overflow-y-auto" gap={ 0 }>
|
||||||
|
{ (mentions.length === 0)
|
||||||
|
? <Text center variant="gray">{ LocalizeText('mentions.window.empty') }</Text>
|
||||||
|
: mentions.map(mention => (
|
||||||
|
<MentionRowView key={ mention.mentionId } mention={ mention } onClick={ onRowClick } />
|
||||||
|
)) }
|
||||||
|
</Flex>
|
||||||
|
{ (mentions.length > 0) &&
|
||||||
|
<Button variant="primary" onClick={ onMarkAll }>{ LocalizeText('mentions.window.markall') }</Button> }
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './MentionRowView';
|
||||||
|
export * from './MentionsView';
|
||||||
|
export * from './useMentionRowClick';
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { CreateLinkEvent, MarkMentionsReadComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { IMentionEntry, SendMessageComposer } from '../../api';
|
||||||
|
import { markRead } from '../../hooks/mentions/mentionsStore';
|
||||||
|
|
||||||
|
// Shared row-click handler used by both MentionsView and the chat-history
|
||||||
|
// "Menzioni" tab so the mark-read + room-navigation behaviour can't diverge.
|
||||||
|
export const useMentionRowClick = (): ((mention: IMentionEntry) => void) =>
|
||||||
|
{
|
||||||
|
return useCallback((mention: IMentionEntry) =>
|
||||||
|
{
|
||||||
|
if(!mention.read)
|
||||||
|
{
|
||||||
|
markRead(mention.mentionId);
|
||||||
|
SendMessageComposer(new MarkMentionsReadComposer(1, mention.mentionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mention.roomId > 0) CreateLinkEvent(`navigator/goto/${ mention.roomId }`);
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import { AnimatePresence, motion, Variants } from 'framer-motion';
|
|||||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { GetConfigurationValue, isHousekeepingEnabled, MessengerIconState, OpenMessengerChat, setYoutubeRoomEnabled, VisitDesktop } from '../../api';
|
import { GetConfigurationValue, isHousekeepingEnabled, MessengerIconState, OpenMessengerChat, setYoutubeRoomEnabled, VisitDesktop } from '../../api';
|
||||||
import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common';
|
import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common';
|
||||||
import { useAchievements, useFriends, useHasPermission, useInventoryUnseenTracker, useMessageEvent, useMessenger, useModTools, useNitroEvent, useSessionInfo, useSoundboard, useWiredTools } from '../../hooks';
|
import { useAchievements, useFriends, useHasPermission, useInventoryUnseenTracker, useMentionsSnapshot, useMessageEvent, useMessenger, useModTools, useNitroEvent, useSessionInfo, useSoundboard, useWiredTools } from '../../hooks';
|
||||||
import { ToolbarItemView } from './ToolbarItemView';
|
import { ToolbarItemView } from './ToolbarItemView';
|
||||||
import { ToolbarMeView } from './ToolbarMeView';
|
import { ToolbarMeView } from './ToolbarMeView';
|
||||||
import { YouTubePlayerView } from './YouTubePlayerView';
|
import { YouTubePlayerView } from './YouTubePlayerView';
|
||||||
@@ -42,6 +42,8 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
|||||||
const { getTotalUnseen = 0 } = useAchievements();
|
const { getTotalUnseen = 0 } = useAchievements();
|
||||||
const { requests = [] } = useFriends();
|
const { requests = [] } = useFriends();
|
||||||
const { iconState = MessengerIconState.HIDDEN } = useMessenger();
|
const { iconState = MessengerIconState.HIDDEN } = useMessenger();
|
||||||
|
const { unreadCount: mentionsUnread = 0 } = useMentionsSnapshot();
|
||||||
|
const mentionsEnabled = useMemo(() => GetConfigurationValue<boolean>('mentions_ui.enabled', true), []);
|
||||||
const { openMonitor, showToolbarButton } = useWiredTools();
|
const { openMonitor, showToolbarButton } = useWiredTools();
|
||||||
const { enabled: soundboardEnabled, reset: resetSoundboard } = useSoundboard();
|
const { enabled: soundboardEnabled, reset: resetSoundboard } = useSoundboard();
|
||||||
const isMod = useHasPermission('acc_supporttool');
|
const isMod = useHasPermission('acc_supporttool');
|
||||||
@@ -332,6 +334,12 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
|||||||
{ (requests.length > 0) &&
|
{ (requests.length > 0) &&
|
||||||
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
{ mentionsEnabled &&
|
||||||
|
<motion.div variants={ itemVariants } className="relative">
|
||||||
|
<ToolbarItemView icon="message" onClick={ () => CreateLinkEvent('mentions/toggle') } className="tb-icon" />
|
||||||
|
{ (mentionsUnread > 0) &&
|
||||||
|
<LayoutItemCountView count={ mentionsUnread } className="absolute -right-2 -top-1" /> }
|
||||||
|
</motion.div> }
|
||||||
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
||||||
<motion.div variants={ itemVariants }>
|
<motion.div variants={ itemVariants }>
|
||||||
<ToolbarItemView className={ `tb-icon ${ iconState === MessengerIconState.UNREAD ? 'is-unseen animate-pulse' : '' }` } icon="message" onClick={ () => OpenMessengerChat() } />
|
<ToolbarItemView className={ `tb-icon ${ iconState === MessengerIconState.UNREAD ? 'is-unseen animate-pulse' : '' }` } icon="message" onClick={ () => OpenMessengerChat() } />
|
||||||
@@ -422,6 +430,12 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
|||||||
{ (requests.length > 0) &&
|
{ (requests.length > 0) &&
|
||||||
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
{ mentionsEnabled &&
|
||||||
|
<motion.div variants={ itemVariants } className="relative">
|
||||||
|
<ToolbarItemView icon="message" onClick={ () => CreateLinkEvent('mentions/toggle') } className="tb-icon" />
|
||||||
|
{ (mentionsUnread > 0) &&
|
||||||
|
<LayoutItemCountView count={ mentionsUnread } className="absolute -right-2 -top-1" /> }
|
||||||
|
</motion.div> }
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
{ /* Mobile side tools — moved out of the bottom bar into a
|
{ /* Mobile side tools — moved out of the bottom bar into a
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { MarkMentionsReadComposer, MentionReceivedEvent, MentionsListEvent, RequestMentionsComposer } from '@nitrots/nitro-renderer';
|
import { MentionReceivedEvent, MentionsListEvent, RequestMentionsComposer } from '@nitrots/nitro-renderer';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { GetConfigurationValue, IMentionEntry, LocalizeText, NotificationBubbleType, PlaySound, SendMessageComposer, SoundNames } from '../../api';
|
import { GetConfigurationValue, IMentionEntry, LocalizeText, NotificationBubbleType, PlaySound, SendMessageComposer, SoundNames } from '../../api';
|
||||||
import { useMessageEvent } from '../events';
|
import { useMessageEvent } from '../events';
|
||||||
import { useNotificationActions } from '../notification';
|
import { useNotificationActions } from '../notification';
|
||||||
import { addMention, setMentions } from './mentionsStore';
|
import { addMention, setMentions } from './mentionsStore';
|
||||||
|
|
||||||
// MarkMentionsReadComposer is part of the mentions wire contract; it is sent by
|
|
||||||
// the UI layer (later phase) when the user opens / clears the mentions window.
|
|
||||||
void MarkMentionsReadComposer;
|
|
||||||
|
|
||||||
export const useMentionMessages = (): void =>
|
export const useMentionMessages = (): void =>
|
||||||
{
|
{
|
||||||
const { showSingleBubble } = useNotificationActions();
|
const { showSingleBubble } = useNotificationActions();
|
||||||
|
|||||||
@@ -251,6 +251,11 @@ export class FlatAccessDeniedMessageEvent extends MessageEvent {}
|
|||||||
export class GenericErrorEvent extends MessageEvent {}
|
export class GenericErrorEvent extends MessageEvent {}
|
||||||
export class GetGuestRoomResultEvent extends MessageEvent {}
|
export class GetGuestRoomResultEvent extends MessageEvent {}
|
||||||
|
|
||||||
|
// Mentions system — incoming events extend MessageEvent (they expose
|
||||||
|
// getParser()); the request/mark composers are symbol-only constructors.
|
||||||
|
export class MentionReceivedEvent extends MessageEvent {}
|
||||||
|
export class MentionsListEvent extends MessageEvent {}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Navigator event classes — MessageEvent subclasses needed by useNavigatorStore
|
// Navigator event classes — MessageEvent subclasses needed by useNavigatorStore
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -377,6 +382,8 @@ export class FollowFriendMessageComposer extends StubClass {}
|
|||||||
export class GetUserEventCatsMessageComposer extends StubClass {}
|
export class GetUserEventCatsMessageComposer extends StubClass {}
|
||||||
export class GetUserFlatCatsMessageComposer extends StubClass {}
|
export class GetUserFlatCatsMessageComposer extends StubClass {}
|
||||||
export class NavigatorSearchComposer extends StubClass {}
|
export class NavigatorSearchComposer extends StubClass {}
|
||||||
|
export class RequestMentionsComposer extends StubClass {}
|
||||||
|
export class MarkMentionsReadComposer extends StubClass {}
|
||||||
export class DesktopViewComposer extends StubClass {}
|
export class DesktopViewComposer extends StubClass {}
|
||||||
export class FurniturePlacePaintComposer extends StubClass {}
|
export class FurniturePlacePaintComposer extends StubClass {}
|
||||||
export class GetGuestRoomMessageComposer extends StubClass {}
|
export class GetGuestRoomMessageComposer extends StubClass {}
|
||||||
|
|||||||
Reference in New Issue
Block a user