diff --git a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.spec.tsx b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.spec.tsx index 23ecd754fe..a53b234907 100644 --- a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.spec.tsx +++ b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.spec.tsx @@ -1,15 +1,19 @@ import React from 'react' -import { instance, mock } from 'ts-mockito' +import { mock } from 'ts-mockito' import { render } from 'uiSrc/utils/test-utils' import MessagesList, { Props } from './MessagesList' -const mockedProps = mock() +const mockedProps = { + ...mock(), + height: 20, + width: 20 +} describe('MessagesList', () => { it('should render', () => { expect( - render() + render() ).toBeTruthy() }) }) diff --git a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.tsx b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.tsx index 1ce1f9b1b9..6e66aee2a1 100644 --- a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.tsx +++ b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/MessagesList.tsx @@ -1,82 +1,149 @@ -import React, { useEffect, useRef } from 'react' -import { CellMeasurer, CellMeasurerCache, List, ListRowProps } from 'react-virtualized' -import AutoSizer from 'react-virtualized-auto-sizer' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { ListChildComponentProps, ListOnScrollProps, VariableSizeList as List } from 'react-window' +import { EuiButton, EuiIcon } from '@elastic/eui' import { getFormatDateTime } from 'uiSrc/utils' import { IMessage } from 'apiSrc/modules/pub-sub/interfaces/message.interface' + import styles from './styles.module.scss' export interface Props { items: IMessage[] + width: number + height: number } const PROTRUDING_OFFSET = 2 +const MIN_ROW_HEIGHT = 30 const MessagesList = (props: Props) => { - const { items = [] } = props - - const cache = new CellMeasurerCache({ - defaultHeight: 17, - fixedWidth: true, - fixedHeight: false - }) + const { items = [], width = 0, height = 0 } = props + const [showAnchor, setShowAnchor] = useState(false) const listRef = useRef(null) + const followRef = useRef(true) + const hasMountedRef = useRef(false) + const rowHeights = useRef<{ [key: number]: number }>({}) + const outerRef = useRef(null) + + useEffect(() => { + scrollToBottom() + }, []) useEffect(() => { - clearCacheAndUpdate() + if (items.length > 0 && followRef.current) { + setTimeout(() => { + scrollToBottom() + }, 0) + } }, [items]) - const clearCacheAndUpdate = () => { - listRef?.current?.scrollToRow(items.length - 1) + useEffect(() => { + if (followRef.current) { + setTimeout(() => { + scrollToBottom() + }, 0) + } + }, [width, height]) + + const getRowHeight = (index: number) => ( + rowHeights.current[index] > MIN_ROW_HEIGHT ? (rowHeights.current[index] + 2) : MIN_ROW_HEIGHT + ) + + const setRowHeight = (index: number, size: number) => { + listRef.current?.resetAfterIndex(0) + rowHeights.current = { ...rowHeights.current, [index]: size } + } + + const scrollToBottom = () => { + listRef.current?.scrollToItem(items.length - 1, 'end') requestAnimationFrame(() => { - listRef?.current?.scrollToRow(items.length - 1) + listRef.current?.scrollToItem(items.length - 1, 'end') }) } - const rowRenderer = ({ parent, index, key, style }: ListRowProps) => { - const { time = 0, channel = '', message = '' } = items[index] + // TODO: delete after manual tests + // const scrollToBottomReserve = () => { + // const { scrollHeight = 0, offsetHeight = 0 } = outerRef.current || {} + + // listRef.current?.scrollTo(scrollHeight - offsetHeight) + // requestAnimationFrame(() => { + // listRef.current?.scrollTo(scrollHeight - offsetHeight) + // }) + // } + + const handleAnchorClick = () => { + scrollToBottom() + } + + const handleScroll = useCallback((e: ListOnScrollProps) => { + if (!hasMountedRef.current) { + hasMountedRef.current = true + return + } + + if (e.scrollUpdateWasRequested === false) { + followRef.current = false + setShowAnchor(true) + } + + if (!outerRef.current) { + return + } + + if (e.scrollOffset + outerRef.current.offsetHeight === outerRef.current.scrollHeight) { + followRef.current = true + setShowAnchor(false) + } + }, []) + + const Row = ({ index, style }: ListChildComponentProps) => { + const rowRef = useRef(null) + + useEffect(() => { + if (rowRef.current) { + setRowHeight(index, rowRef.current?.clientHeight) + } + }, [rowRef]) + + const { channel, message, time } = items[index] + return ( - - {({ registerChild, measure }) => ( -
-
{getFormatDateTime(time)}
-
{channel}
-
{message}
-
- )} -
+
+
{getFormatDateTime(time)}
+
{channel}
+
{message}
+
) } return ( <> -
-
Timestamp
-
Channel
-
Message
-
- - {({ width, height }) => ( - - )} - + + {Row} + + {showAnchor && ( + + New messages + + + )} ) } diff --git a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/styles.module.scss b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/styles.module.scss index 27ab3fd493..820fdd13be 100644 --- a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/styles.module.scss +++ b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesList/styles.module.scss @@ -1,3 +1,7 @@ +@import "@elastic/eui/src/global_styling/mixins/helpers"; +@import "@elastic/eui/src/components/table/mixins"; +@import "@elastic/eui/src/global_styling/index"; + .time, .channel, .message { @@ -26,10 +30,14 @@ .message { width: calc(100% - 372px); color: var(--htmlColor); - word-break: break-all; + word-break: break-word; } .header { + width: 100%; + display: flex; + height: 24px; + .time, .channel, .message { @@ -38,3 +46,49 @@ color: var(--htmlColor); } } + +.wrapperContainer { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} + +.listContainer { + height: 100%; + width: 100%; + padding-top: 14px; + padding-right: 6px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.listContent { + @include euiScrollBar; +} + +.anchorBtn { + position: absolute; + z-index: 10; + bottom: 10px; + right: 28px; + width: 149px !important; + height: 36px !important; + + box-shadow: 0px 3px 6px #00000099 !important; + border-radius: 18px !important; + + :global(.euiButton__text) { + padding-left: 6px; + font-size: 12px !important; + font-weight: normal !important; + } + + svg { + margin-left: 4px; + margin-bottom: 1px; + width: 20px; + height: 20px; + } +} diff --git a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesListWrapper.tsx b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesListWrapper.tsx index 6a04a47657..3c57a78c27 100644 --- a/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesListWrapper.tsx +++ b/redisinsight/ui/src/pages/pubSub/components/messages-list/MessagesListWrapper.tsx @@ -1,17 +1,40 @@ import React from 'react' import { useSelector } from 'react-redux' +import AutoSizer from 'react-virtualized-auto-sizer' + import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { pubSubSelector } from 'uiSrc/slices/pubsub/pubsub' import EmptyMessagesList from './EmptyMessagesList' import MessagesList from './MessagesList' +import styles from './MessagesList/styles.module.scss' + const MessagesListWrapper = () => { const { messages = [], isSubscribed } = useSelector(pubSubSelector) const { connectionType } = useSelector(connectedInstanceSelector) return ( <> - {(messages.length > 0 || isSubscribed) && } + {(messages.length > 0 || isSubscribed) && ( +
+
+
Timestamp
+
Channel
+
Message
+
+
+ + {({ width, height }) => ( + + )} + +
+
+ )} {messages.length === 0 && !isSubscribed && } ) diff --git a/redisinsight/ui/src/pages/pubSub/styles.module.scss b/redisinsight/ui/src/pages/pubSub/styles.module.scss index 803268b312..80f12ee5e2 100644 --- a/redisinsight/ui/src/pages/pubSub/styles.module.scss +++ b/redisinsight/ui/src/pages/pubSub/styles.module.scss @@ -34,8 +34,8 @@ .tableWrapper { width: 100%; - height: calc(100% - 135px); - padding: 18px 0 18px 18px; + height: calc(100% - 125px); + padding: 18px 0 0 18px; :global(.ReactVirtualized__Grid) { @include euiScrollBar;