mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
rendering customized virtual list
This commit is contained in:
parent
43d88b5a64
commit
b845404048
@ -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 (
|
||||
<CellMeasurer
|
||||
cache={cache}
|
||||
columnIndex={0}
|
||||
key={key}
|
||||
parent={parent}
|
||||
rowIndex={index}
|
||||
>
|
||||
{({ measure, registerChild }) => (
|
||||
// 'style' attribute required to position cell (within parent List)
|
||||
<div class="noselect" ref={registerChild} style={style}>
|
||||
{ state.topics[index].data.topicDetail.data }
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConversationWrapper>
|
||||
<div class="header">
|
||||
@ -47,22 +18,14 @@ export function Conversation() {
|
||||
<CloseButton type="text" class="close" size={'large'} onClick={() => actions.close()} icon={<CloseOutlined />} />
|
||||
</div>
|
||||
<div class="thread">
|
||||
<div style={{ flex: '1 1 auto' }}>
|
||||
<AutoSizer>
|
||||
{({height, width}) => (
|
||||
<List
|
||||
width={width}
|
||||
height={height}
|
||||
deferredMeasurementCache={cache}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={renderRow}
|
||||
rowCount={state.topics.length}
|
||||
overscanRowCount={16}
|
||||
scrollToIndex={scrollIndex}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<VirtualList topics={[
|
||||
'OLDEST TOPIC',
|
||||
'NEXT TOPIC',
|
||||
'NEXT TOPIC',
|
||||
'NEXT TOPIC',
|
||||
'NEXT TOPIC',
|
||||
'NEWEST TOPIC',
|
||||
]}/>
|
||||
</div>
|
||||
<AddTopic />
|
||||
</ConversationWrapper>
|
||||
|
@ -43,7 +43,6 @@ export const ConversationWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding-left: 16px;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -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 (
|
||||
<ReactResizeDetector handleHeight={true}>
|
||||
{({ height }) => {
|
||||
if (typeof height !== 'undefined' && height > 0) {
|
||||
onHeight(height);
|
||||
}
|
||||
return (
|
||||
<TopicItemWrapper>
|
||||
<div>topic</div>
|
||||
</TopicItemWrapper>
|
||||
)
|
||||
}}
|
||||
</ReactResizeDetector>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TopicItemWrapper = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
205
net/web/src/User/Conversation/VirtualList/VirtualList.jsx
Normal file
205
net/web/src/User/Conversation/VirtualList/VirtualList.jsx
Normal file
@ -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 (
|
||||
<div style={{ position: 'absolute', top: container.top }}>
|
||||
<TopicItem topic={topics[container.index]} onHeight={(height) => onTopicHeight(container, height)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<ReactResizeDetector handleHeight={true}>
|
||||
{({ height }) => {
|
||||
setViewHeight(height);
|
||||
return (
|
||||
<VirtualListWrapper onScroll={onScrollView}>
|
||||
<div class="rollview" ref={listRef} onScroll={onScrollView}>
|
||||
<div class="roll" style={{ height: canvasHeight }}>
|
||||
{ items }
|
||||
</div>
|
||||
</div>
|
||||
</VirtualListWrapper>
|
||||
)
|
||||
}}
|
||||
</ReactResizeDetector>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user