diff --git a/net/web/src/User/Conversation/Conversation.jsx b/net/web/src/User/Conversation/Conversation.jsx index b298b182..9dece493 100644 --- a/net/web/src/User/Conversation/Conversation.jsx +++ b/net/web/src/User/Conversation/Conversation.jsx @@ -5,41 +5,12 @@ import { Button, Checkbox, Modal } from 'antd' import { ConversationWrapper, CloseButton, ListItem } from './Conversation.styled'; import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; import { AddTopic } from './AddTopic/AddTopic'; +import { VirtualList } from './VirtualList/VirtualList'; export function Conversation() { - const [ scrollIndex, setScrollIndex ] = useState(null); const { state, actions } = useConversation(); - const cache = new CellMeasurerCache({ - defaultHeight: 256, - fixedWidth: true - }); - - useEffect(() => { - setScrollIndex(state.topics.length); - }, [state]) - - const renderRow = ({ index, isScrolling, key, parent, style }) => { - - return ( - - {({ measure, registerChild }) => ( - // 'style' attribute required to position cell (within parent List) -
- { state.topics[index].data.topicDetail.data } -
- )} -
- ); - } - return (
@@ -47,22 +18,14 @@ export function Conversation() { actions.close()} icon={} />
-
- - {({height, width}) => ( - - )} - -
+
diff --git a/net/web/src/User/Conversation/Conversation.styled.jsx b/net/web/src/User/Conversation/Conversation.styled.jsx index 4d3c3596..41875220 100644 --- a/net/web/src/User/Conversation/Conversation.styled.jsx +++ b/net/web/src/User/Conversation/Conversation.styled.jsx @@ -43,7 +43,6 @@ export const ConversationWrapper = styled.div` display: flex; flex-grow: 1; flex-direction: column; - padding-left: 16px; width: 100%; overflow: auto; } diff --git a/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.jsx b/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.jsx new file mode 100644 index 00000000..a5becf46 --- /dev/null +++ b/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.jsx @@ -0,0 +1,22 @@ +import React, { useEffect } from 'react'; +import { TopicItemWrapper } from './TopicItem.styled'; +import ReactResizeDetector from 'react-resize-detector'; + +export function TopicItem({ topic, onHeight }) { + + return ( + + {({ height }) => { + if (typeof height !== 'undefined' && height > 0) { + onHeight(height); + } + return ( + +
topic
+
+ ) + }} +
+ ) +} + diff --git a/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.styled.js b/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.styled.js new file mode 100644 index 00000000..a5bbbb91 --- /dev/null +++ b/net/web/src/User/Conversation/VirtualList/TopicItem/TopicItem.styled.js @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +export const TopicItemWrapper = styled.div` + width: 100%; +`; + + diff --git a/net/web/src/User/Conversation/VirtualList/VirtualList.jsx b/net/web/src/User/Conversation/VirtualList/VirtualList.jsx new file mode 100644 index 00000000..85a41715 --- /dev/null +++ b/net/web/src/User/Conversation/VirtualList/VirtualList.jsx @@ -0,0 +1,205 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { VirtualListWrapper } from './VirtualList.styled'; +import ReactResizeDetector from 'react-resize-detector'; +import { TopicItem } from './TopicItem/TopicItem'; + +export function VirtualList({ topics }) { + + const OVERSCAN = 300 + const DEFAULT_ITEM_HEIGHT = 64; + const DEFAULT_LIST_HEIGHT = 1024; + + const [ viewHeight, setViewHeight ] = useState(DEFAULT_LIST_HEIGHT); + const [ canvasHeight, setCanvasHeight ] = useState(DEFAULT_LIST_HEIGHT*3); + const [ scrolling, setScrolling ] = useState(false); + const [ items, setItems ] = useState([]); + + let scrollTop = useRef(0); + let containers = useRef([]); + let anchor = useRef(null); + let listRef = useRef(); + + const addItemTop = (item) => { + setItems((i) => { i.unshift(item); return [...i] }); + } + + const addItemBottom = (item) => { + setItems((i) => { i.push(item); return [...i] }); + } + + const updateItem = (idx, item) => { + setItems((i) => { i[idx] = item; return [...i] }); + } + + useEffect(() => { + if (viewHeight * 3 > canvasHeight) { + setCanvasHeight(viewHeight*3); + } + setTopics(); + }, [viewHeight]); + + useEffect(() => { + setTopics(); + }, [topics]); + + const onScrollView = (e) => { + + // add or remove from overscan + + // clip to top or bottom + + // set or clear latch + + scrollTop.current = e.target.scrollTop; + } + + const loadNextItem = () => { + let view = getPlacement(); + if (view) { + if (view.overscan.top < OVERSCAN) { + if (containers.current[0].index > 0) { + let container = { + top: containers.current[0].top - DEFAULT_ITEM_HEIGHT, + height: DEFAULT_ITEM_HEIGHT, + index: containers.current[0].index - 1, + } + anchor.current += 1; + containers.current.unshift(container); +console.log("ADD ITEM BEFORE", container); + addItemTop(getItem(container)) + } + } + if (view.overscan.bottom < OVERSCAN) { + if (containers.current[containers.current.length - 1].index + 1 < topics.length) { +console.log("ADD ITEM AFTER"); + let container = { + top: containers.current[containers.current.length - 1].top + containers.current[containers.current.length - 1].height, + height: DEFAULT_ITEM_HEIGHT, + index: containers.current[containers.current.length - 1].index + 1, + } + containers.current.push(container); + addItemBottom(getItem(container)) + } + } + } + } + + const alignItems = () => { + if (containers.current.length > 0) { + let pos = null; + + pos = containers.current[anchor.current].top; + for (let i = anchor.current - 1; i >= 0; i--) { + pos -= containers.current[i].height; + if (containers.current[i].top != pos) { + containers.current[i].top = pos; + updateItem(i, getItem(containers.current[i])); + } + } + + if (pos < 0) { + // TODO reset canvas + console.log("ALERT: reset convas"); + } + + pos = containers.current[anchor.current].top + containers.current[anchor.current].height; + for (let i = anchor.current + 1; i < containers.current.length; i++) { + if (containers.current[i].top != pos) { + containers.current[i].top = pos; + updateItem(i, getItem(containers.current[i])); + } + pos += containers.current[i].height; + } + + if (pos > canvasHeight) { + // TODO reset canvas + console.log("ALERT: reset canvas"); + } + + let view = getPlacement(); + if (!scrolling) { + if (view.position.height < viewHeight) { + listRef.current.scrollTo({ top: view.position.top, left: 0, behavior: 'smooth' }); + } + else { + listRef.current.scrollTo({ top: view.position.bottom - viewHeight, left: 0, behavior: 'smooth' }); + } + } + } + + loadNextItem(); + } + + const setTopics = () => { + // validate items + + if (topics.length > 0 && canvasHeight > 0) { + let view = getPlacement(); + if (!view) { + let pos = canvasHeight / 2; + listRef.current.scrollTo({ top: pos, left: 0 }); + scrollTop.current = pos; + + let container = { + top: pos - DEFAULT_ITEM_HEIGHT, + height: DEFAULT_ITEM_HEIGHT, + index: topics.length - 1, + } + + anchor.current = 0; + containers.current.push(container); + addItemBottom(getItem(container)); + + listRef.current.scrollTo({ top: container.top, left: 0, behavior: 'smooth' }); + } + else { + loadNextItem(); + } + } + } + + const onTopicHeight = (container, height) => { + container.height = height; + alignItems(); + } + + const getItem = (container) => { + + return ( +
+ onTopicHeight(container, height)} /> +
+ ) + } + + const getPlacement = () => { + if (containers.current.length == 0) { + return null; + } + let top = containers.current[0].top; + let bottom = containers.current[containers.current.length-1].top + containers.current[containers.current.length-1].height; + let overTop = scrollTop.current - top; + let overBottom = bottom - (scrollTop.current + viewHeight); + return { + position: { top, bottom, height: bottom - top }, + overscan: { top: overTop, bottom: overBottom } + }; + } + + return ( + + {({ height }) => { + setViewHeight(height); + return ( + +
+
+ { items } +
+
+
+ ) + }} +
+ ) +} diff --git a/net/web/src/User/Conversation/VirtualList/VirtualList.styled.js b/net/web/src/User/Conversation/VirtualList/VirtualList.styled.js new file mode 100644 index 00000000..6593ed2e --- /dev/null +++ b/net/web/src/User/Conversation/VirtualList/VirtualList.styled.js @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +export const VirtualListWrapper = styled.div` + width: 100%; + height: 100%; + background-color: #f6f5ed; + overflow: hidden; + + .rollview { + overflow-y: auto; + width: 100%; + height: 100%; + } + + .roll { + width: 100%; + position: relative; + } +`; + +