55// License, v2.0. If a copy of the MPL was not distributed with this
66// file, You can obtain one at http://mozilla.org/MPL/2.0/
77
8- import { ReOrderDotsVertical16Regular } from "@fluentui/react-icons" ;
9- import { Badge , Typography } from "@mui/material" ;
8+ import {
9+ ChevronLeft16Regular ,
10+ ChevronRight16Regular ,
11+ ReOrderDotsVertical16Regular ,
12+ } from "@fluentui/react-icons" ;
13+ import { Badge , IconButton , Tooltip , Typography } from "@mui/material" ;
1014import { FzfResultItem } from "fzf" ;
1115import { useCallback , useMemo } from "react" ;
1216
1317import { quoteTopicNameIfNeeded } from "@lichtblick/message-path" ;
1418import { HighlightChars } from "@lichtblick/suite-base/components/HighlightChars" ;
19+ import { useMessagePipeline } from "@lichtblick/suite-base/components/MessagePipeline" ;
20+ import { MessagePipelineContext } from "@lichtblick/suite-base/components/MessagePipeline/types" ;
1521import { DraggedMessagePath } from "@lichtblick/suite-base/components/PanelExtensionAdapter" ;
1622import Stack from "@lichtblick/suite-base/components/Stack" ;
1723import { Topic } from "@lichtblick/suite-base/players/types" ;
1824import { useMessagePathDrag } from "@lichtblick/suite-base/services/messagePathDragging" ;
1925
2026import { TopicStatsChip } from "./TopicStatsChip" ;
2127import { useTopicListStyles } from "./useTopicListStyles" ;
28+ import { useTopicMessageNavigation } from "./useTopicMessageNavigation" ;
29+
30+ const selectSubscriptions = ( ctx : MessagePipelineContext ) => ctx . subscriptions ;
2231
2332export function TopicRow ( {
2433 topicResult,
@@ -37,6 +46,39 @@ export function TopicRow({
3746
3847 const topic = topicResult . item ;
3948
49+ const subscriptions = useMessagePipeline ( selectSubscriptions ) ;
50+
51+ const isTopicSubscribed = useMemo (
52+ ( ) => subscriptions . some ( ( sub ) => sub . topic === topic . name ) ,
53+ [ subscriptions , topic . name ] ,
54+ ) ;
55+
56+ const {
57+ handleNextMessage,
58+ handlePreviousMessage,
59+ isNavigating,
60+ canNavigateNext,
61+ canNavigatePrevious,
62+ } = useTopicMessageNavigation ( {
63+ topicName : topic . name ,
64+ isTopicSubscribed,
65+ selected,
66+ } ) ;
67+
68+ const isNavigationDisabled = useMemo (
69+ ( ) => isNavigating || ! selected || ! isTopicSubscribed ,
70+ [ isNavigating , selected , isTopicSubscribed ] ,
71+ ) ;
72+
73+ const isPreviousDisabled = useMemo (
74+ ( ) => isNavigationDisabled || ! canNavigatePrevious ,
75+ [ isNavigationDisabled , canNavigatePrevious ] ,
76+ ) ;
77+ const isNextDisabled = useMemo (
78+ ( ) => isNavigationDisabled || ! canNavigateNext ,
79+ [ isNavigationDisabled , canNavigateNext ] ,
80+ ) ;
81+
4082 const item : DraggedMessagePath = useMemo (
4183 ( ) => ( {
4284 path : quoteTopicNameIfNeeded ( topic . name ) ,
@@ -47,6 +89,7 @@ export function TopicRow({
4789 } ) ,
4890 [ topic . name , topic . schemaName ] ,
4991 ) ;
92+
5093 const { connectDragSource, connectDragPreview, cursor, isDragging, draggedItemCount } =
5194 useMessagePathDrag ( {
5295 item,
@@ -61,6 +104,26 @@ export function TopicRow({
61104 [ connectDragPreview , connectDragSource ] ,
62105 ) ;
63106
107+ const seekToNextMessage = useCallback (
108+ ( event : React . MouseEvent ) => {
109+ event . stopPropagation ( ) ;
110+ void handleNextMessage ( ) ;
111+ } ,
112+ [ handleNextMessage ] ,
113+ ) ;
114+
115+ const seekToPreviousMessage = useCallback (
116+ ( event : React . MouseEvent ) => {
117+ event . stopPropagation ( ) ;
118+ void handlePreviousMessage ( ) ;
119+ } ,
120+ [ handlePreviousMessage ] ,
121+ ) ;
122+
123+ const handleButtonMouseDown = useCallback ( ( event : React . MouseEvent ) => {
124+ event . stopPropagation ( ) ;
125+ } , [ ] ) ;
126+
64127 return (
65128 < div
66129 ref = { combinedRef }
@@ -100,7 +163,39 @@ export function TopicRow({
100163 </ Typography >
101164 ) }
102165 </ Stack >
103- < TopicStatsChip selected = { selected } topicName = { topic . name } />
166+ < Stack direction = "column" alignItems = "center" gap = { 0.5 } >
167+ < TopicStatsChip selected = { selected } topicName = { topic . name } />
168+ < Stack direction = "row" gap = { 0.5 } alignItems = "center" >
169+ < Tooltip title = { isPreviousDisabled ? "" : "Previous message" } >
170+ < span >
171+ < IconButton
172+ size = "small"
173+ aria-label = "Previous message"
174+ onClick = { seekToPreviousMessage }
175+ onMouseDown = { handleButtonMouseDown }
176+ disabled = { isPreviousDisabled }
177+ className = { classes . navIconButton }
178+ >
179+ < ChevronLeft16Regular />
180+ </ IconButton >
181+ </ span >
182+ </ Tooltip >
183+ < Tooltip title = { isNextDisabled ? "" : "Next message" } >
184+ < span >
185+ < IconButton
186+ size = "small"
187+ aria-label = "Next message"
188+ onClick = { seekToNextMessage }
189+ onMouseDown = { handleButtonMouseDown }
190+ disabled = { isNextDisabled }
191+ className = { classes . navIconButton }
192+ >
193+ < ChevronRight16Regular />
194+ </ IconButton >
195+ </ span >
196+ </ Tooltip >
197+ </ Stack >
198+ </ Stack >
104199 < div data-testid = "TopicListDragHandle" className = { classes . dragHandle } >
105200 < ReOrderDotsVertical16Regular />
106201 </ div >
0 commit comments