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 (
-
-
- {({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 (
+
+
+
+ )
+ }}
+
+ )
+}
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;
+ }
+`;
+
+