From 4fdba26371434bfe6a58936430db2802a564996c Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Thu, 27 Oct 2022 15:21:46 -0700 Subject: [PATCH] removing custom implementation of virtuallist now that i discovered flex-column-reverse (DUH!) --- .../context/useConversationContext.hook.js | 13 +- .../src/session/conversation/Conversation.jsx | 21 +- .../conversation/Conversation.styled.js | 3 + .../conversation/useConversation.hook.js | 1 + .../conversation/virtualList/VirtualList.jsx | 392 ------------------ .../virtualList/VirtualList.styled.js | 33 -- .../virtualList/useVirtualList.hook.js | 48 --- 7 files changed, 30 insertions(+), 481 deletions(-) delete mode 100644 net/web/src/session/conversation/virtualList/VirtualList.jsx delete mode 100644 net/web/src/session/conversation/virtualList/VirtualList.styled.js delete mode 100644 net/web/src/session/conversation/virtualList/useVirtualList.hook.js diff --git a/net/web/src/context/useConversationContext.hook.js b/net/web/src/context/useConversationContext.hook.js index 87560918..916a0ef1 100644 --- a/net/web/src/context/useConversationContext.hook.js +++ b/net/web/src/context/useConversationContext.hook.js @@ -44,6 +44,7 @@ export function useConversationContext() { const profile = useContext(ProfileContext); const topics = useRef(new Map()); const view = useRef(0); + const more = useRef(true); const serialize = useRef(0); const updateState = (value) => { @@ -268,9 +269,15 @@ export function useConversationContext() { updateConversation(); }, addHistory: () => { - updateState({ loadingMore: true }); - events.current.push({ type: EVENT_MORE }); - updateConversation(); + if (more.current && !state.loadingMore) { + more.current = false; + updateState({ loadingMore: true }); + events.current.push({ type: EVENT_MORE }); + updateConversation(); + setTimeout(() => { + more.current = true; + }, 2000); + } }, setChannelSubject: async (subject) => { return await channel.actions.setChannelSubject(channelView.current.channelId, subject); diff --git a/net/web/src/session/conversation/Conversation.jsx b/net/web/src/session/conversation/Conversation.jsx index 5b8efdd4..6b5c233b 100644 --- a/net/web/src/session/conversation/Conversation.jsx +++ b/net/web/src/session/conversation/Conversation.jsx @@ -1,20 +1,31 @@ +import { useRef } from 'react'; import { ConversationWrapper, StatusError } from './Conversation.styled'; import { ExclamationCircleOutlined, SettingOutlined, CloseOutlined } from '@ant-design/icons'; import { useConversation } from './useConversation.hook'; import { Logo } from 'logo/Logo'; import { AddTopic } from './addTopic/AddTopic'; -import { VirtualList } from './virtualList/VirtualList'; import { TopicItem } from './topicItem/TopicItem'; -import { Spin, Tooltip } from 'antd'; +import { List, Spin, Tooltip } from 'antd'; export function Conversation({ closeConversation, openDetails, cardId, channelId }) { const { state, actions } = useConversation(cardId, channelId); + const thread = useRef(null); const topicRenderer = (topic) => { return () } + const scrollThread = (e) => { + const content = thread.current?.scrollHeight; + const frame = thread.current?.clientHeight; + const position = e.target.scrollTop; + const above = content - (Math.abs(position) + frame); + if (above < 1024) { + actions.more(); + } + }; + return (
@@ -47,9 +58,9 @@ export function Conversation({ closeConversation, openDetails, cardId, channelId
)} -
- +
+ { state.loadingInit && (
diff --git a/net/web/src/session/conversation/Conversation.styled.js b/net/web/src/session/conversation/Conversation.styled.js index 1c1ff574..16d0aa05 100644 --- a/net/web/src/session/conversation/Conversation.styled.js +++ b/net/web/src/session/conversation/Conversation.styled.js @@ -54,6 +54,9 @@ export const ConversationWrapper = styled.div` position: relative; flex-grow: 1; min-height: 0; + overflow: auto; + display: flex; + flex-direction: column-reverse; .loading { position: absolute; diff --git a/net/web/src/session/conversation/useConversation.hook.js b/net/web/src/session/conversation/useConversation.hook.js index e0671acd..d3cf680c 100644 --- a/net/web/src/session/conversation/useConversation.hook.js +++ b/net/web/src/session/conversation/useConversation.hook.js @@ -105,6 +105,7 @@ export function useConversation(cardId, channelId) { }, [cardId, channelId, card, channel]); useEffect(() => { + updateState({ topics: [] }); conversation.actions.setConversationId(cardId, channelId); // eslint-disable-next-line }, [cardId, channelId]); diff --git a/net/web/src/session/conversation/virtualList/VirtualList.jsx b/net/web/src/session/conversation/virtualList/VirtualList.jsx deleted file mode 100644 index e1b8162c..00000000 --- a/net/web/src/session/conversation/virtualList/VirtualList.jsx +++ /dev/null @@ -1,392 +0,0 @@ -import React, { useRef, useState, useEffect } from 'react'; -import { VirtualListWrapper, VirtualItem } from './VirtualList.styled'; -import ReactResizeDetector from 'react-resize-detector'; -import { useVirtualList } from './useVirtualList.hook'; - -export function VirtualList({ id, items, itemRenderer, loadMore }) { - - const redZone = 1024; - const holdZone = 2048; - const fillZone = 1024; - - const pushDelay = 250; - const moreDelay = 2000; - const latchDelay = 500; - - const pad = 4; - const defaultHeight = 128; - const rollHeight = 16384; - - const { state, actions } = useVirtualList(); - const containers = useRef([]); - const itemView = useRef([]); - const debounce = useRef([]); - const nomore = useRef(false); - const nolatch = useRef(false); - const list = useRef(null); - const scrollTop = useRef(0); - const latched = useRef(true); - - const [scrollPos, setScrollPos] = useState(0); - - useEffect(() => { - latched.current = true; - actions.clearSlots(); - scrollTop.current = 8192; - list.current.scrollTo({ top: 8192, left: 0 }); - // eslint-disable-next-line - }, [id]); - - useEffect(() => { - - // reference copy - itemView.current = items; - - // genearte set of active ids - let ids = new Map(); - for (let i = 0; i < itemView.current.length; i++) { - ids.set(getItemKey(itemView.current[i]), i); - } - - // remove any deleted items - let slots = []; - containers.current.forEach((container) => { - if (!ids.has(container.key)) { - actions.removeSlot(container.key); - } - else { - container.index = ids.get(container.key); - container.item = itemView.current[container.index]; - slots.push(container); - } - }); - containers.current = slots; - - // sort by index - containers.current.sort((a, b) => { - if (a.index < b.index) { - return -1; - } - return 1; - }); - - // rerender list - layoutItems(); - // eslint-disable-next-line - }, [items]); - - useEffect(() => { - layoutItems(); - // eslint-disable-next-line - }, [scrollPos, state.listHeight, state.view]); - - const layoutItems = () => { - alignSlots(); - loadSlots(); - releaseSlots(); - centerSlots(); - latchSlots(); - pushSlots(); - }; - - const latchSlots = () => { - if (containers.current.length > 0 && latched.current && state.listHeight > 0) { - if (!nolatch.current) { - const last = containers.current[containers.current.length - 1]; - const bottom = last.top + last.height; - if (last.heightSet && scrollTop.current < bottom - (state.listHeight - pad)) { - list.current.scrollTo({ top: bottom - (state.listHeight - pad), left: 0, behavior: 'smooth' }); - nolatch.current = true; - setTimeout(() => { - nolatch.current = false; - latchSlots(); - }, latchDelay); - } - } - } - } - - const pushSlots = () => { - if (debounce.current != null) { - clearTimeout(debounce.current); - }; - debounce.current = setTimeout(() => { - if (containers.current.length > 0) { - const range = getContainerRange(); - if (range.bottom - range.top < (state.listHeight - pad)) { - if (scrollTop.current + (state.listHeight - pad) !== range.bottom) { - list.current.scrollTo({ top: range.bottom - (state.listHeight - pad), left: 0 }); - latched.current = true; - } - } - else if (scrollTop.current + (state.listHeight - pad) > range.bottom) { - list.current.scrollTo({ top: range.bottom - (state.listHeight - pad), left: 0 }); - latched.current = true; - } - else if (scrollTop.current < range.top) { - list.current.scrollTo({ top: range.top, left: 0 }); - } - } - }, pushDelay); - }; - - const alignSlots = () => { - if (containers.current.length > 1) { - if (latched.current) { - const index = containers.current.length - 1; - const last = containers.current[index]; - let bottom = last.top + last.height; - for (let i = index; i >= 0; i--) { - let container = containers.current[i]; - if (container.top + container.height !== bottom) { - container.top = bottom - container.height; - actions.updateSlot(container.key, getSlot(container)); - } - bottom -= container.height; - } - } - else { - const index = Math.floor(containers.current.length / 2); - const mid = containers.current[index]; - let top = mid.top; - for (let i = index; i < containers.current.length; i++) { - let container = containers.current[i]; - if (container.top !== top) { - container.top = top; - actions.updateSlot(container.key, getSlot(container)); - } - top += container.height; - } - let bottom = mid.top + mid.height; - for (let i = index; i >= 0; i--) { - let container = containers.current[i]; - if (container.top + container.height !== bottom) { - container.top = bottom - container.height; - actions.updateSlot(container.key, getSlot(container)); - } - bottom -= container.height; - } - } - } - } - - const loadSlots = () => { - - if (state.listHeight === 0) { - return; - } - - if (containers.current.length === 0) { - // add the first slot - if (itemView.current.length > 0) { - let item = itemView.current[itemView.current.length - 1]; - let slot = { - top: rollHeight / 2 + (state.listHeight - pad), - height: defaultHeight, - heightSet: false, - key: getItemKey(item), - index: itemView.current.length - 1, - item: item, - } - containers.current.push(slot); - actions.addSlot(slot.key, getSlot(slot)); - list.current.scrollTo({ top: rollHeight / 2, left: 0 }); - } - } - else { - // fill in any missing slots - let index = containers.current[0].index; - for (let i = 1; i < containers.current.length; i++) { - let container = containers.current[i]; - if (container.index !== index + i) { - const item = itemView.current[index + i]; - let slot = { - top: container.top - defaultHeight, - height: defaultHeight, - heightSet: false, - index: index + i, - item: item, - key: getItemKey(item), - } - containers.current.splice(i, 0, slot); - actions.addSlot(slot.key, getSlot(slot)); - } - } - } - - loadSlotAbove(); - loadSlotBelow(); - }; - - const loadSlotAbove = () => { - if (containers.current.length > 0) { - const range = getContainerRange(); - if (scrollTop.current - fillZone < range.top) { - const container = containers.current[0]; - if (container.index > 0) { - const index = container.index - 1; - const item = itemView.current[index]; - let slot = { - top: container.top - defaultHeight, - height: defaultHeight, - heightSet: false, - index: index, - item: item, - key: getItemKey(item), - } - containers.current.unshift(slot); - actions.addSlot(slot.key, getSlot(slot)); - loadSlotAbove(); - } - } - } - } - - const loadSlotBelow = () => { - if (containers.current.length > 0) { - const container = containers.current[containers.current.length - 1]; - if (container.index + 1 < itemView.current.length) { - const range = getContainerRange(); - if (scrollTop.current + (state.listHeight - pad) + fillZone > range.bottom) { - const index = container.index + 1; - const item = itemView.current[index]; - let slot = { - top: container.top + container.height, - height: defaultHeight, - heightSet: false, - index: index, - item: item, - key: getItemKey(item), - } - containers.current.push(slot); - actions.addSlot(slot.key, getSlot(slot)); - loadSlotBelow(); - } - } - } - } - - const releaseSlots = () => { - releaseSlotAbove(); - releaseSlotBelow(); - }; - - const releaseSlotAbove = () => { - if (containers.current.length > 1) { - const container = containers.current[0]; - if (container.top + container.height < scrollTop.current - holdZone) { - actions.removeSlot(container.key); - containers.current.shift(); - releaseSlotAbove(); - } - } - } - - const releaseSlotBelow = () => { - if (containers.current.length > 1) { - const container = containers.current[containers.current.length - 1]; - if (container.top > scrollTop.current + state.listHeight + holdZone) { - actions.removeSlot(container.key); - containers.current.pop(); - releaseSlotBelow(); - } - } - } - - const centerSlots = () => { - if (containers.current.length > 0) { - const top = containers.current[0]; - if (top.top < redZone) { - containers.current.forEach(container => { - container.top += rollHeight / 2; - actions.updateSlot(container.key, getSlot(container)); - }); - list.current.scrollTo({ top: scrollTop.current + rollHeight / 2, left: 0 }); - } - - const bottom = containers.current[containers.current.length - 1]; - if (bottom.top + bottom.height > rollHeight - redZone) { - containers.current.forEach(container => { - container.top -= rollHeight / 2; - actions.updateSlot(container.key, getSlot(container)); - }); - list.current.scrollTo({ top: scrollTop.current - rollHeight / 2, left: 0 }); - } - } - }; - - const getItemKey = (item) => { - return `${id}.${item.id}.${item.revision}` - } - - const getSlot = (item) => { - const container = item; - return ( - - - {({ height }) => { - if (typeof height !== 'undefined' && container.height !== height) { - container.height = height; - container.heightSet = true; - layoutItems(); - } - return itemRenderer(container.item); - }} - - - ) - } - - const getContainerRange = () => { - let top = rollHeight; - let bottom = 0; - containers.current.forEach((c) => { - if (c.top < top) { - top = c.top; - } - if (c.top + c.height > bottom) { - bottom = c.top + c.height; - } - }); - return { top, bottom }; - } - - const scrollView = (e) => { - if (containers.current.length > 0 && containers.current[0].index === 0 && !nomore.current) { - loadMore(); - nomore.current = true; - setTimeout(() => { - nomore.current = false; - }, moreDelay); - } - - scrollTop.current = e.target.scrollTop; - setScrollPos(e.target.scrollTop); - } - - const unlatch = () => { - latched.current = false; - } - - return ( -
- - {({ height }) => { - if (height && state.listHeight !== height) { - actions.setListHeight(height); - } - return ( - -
-
- { state.slots } -
-
-
- ) - }} -
-
- ) -} diff --git a/net/web/src/session/conversation/virtualList/VirtualList.styled.js b/net/web/src/session/conversation/virtualList/VirtualList.styled.js deleted file mode 100644 index b8df2f93..00000000 --- a/net/web/src/session/conversation/virtualList/VirtualList.styled.js +++ /dev/null @@ -1,33 +0,0 @@ -import styled from 'styled-components'; - -export const VirtualListWrapper = styled.div` - width: 100%; - height: 100%; - overflow: hidden; - - .rollview { - width: 100%; - height: 100%; - overflow-y: auto; - - /* hide scrollbar for IE, Edge and Firefox */ - -ms-overflow-style: none; - scrollbar-width: none; - } - - .rollview::-webkit-scrollbar { - display: none; - } - - .roll { - width: 100%; - position: relative; - } -`; - -export const VirtualItem = styled.div` - position: absolute; - width: 100%; - overflow: hidden; - max-height: 1024px; -`; diff --git a/net/web/src/session/conversation/virtualList/useVirtualList.hook.js b/net/web/src/session/conversation/virtualList/useVirtualList.hook.js deleted file mode 100644 index 33f33884..00000000 --- a/net/web/src/session/conversation/virtualList/useVirtualList.hook.js +++ /dev/null @@ -1,48 +0,0 @@ -import { useState, useRef } from 'react'; - -export function useVirtualList(id) { - - const [state, setState] = useState({ - view: null, - listHeight: 0, - slots: [], - }); - - const slots = useRef(new Map()); - - const updateState = (value) => { - setState((s) => ({ ...s, ...value })); - } - - const actions = { - setView: (view) => { - updateState({ view }); - }, - setListHeight: (listHeight) => { - updateState({ listHeight: listHeight }); - }, - addSlot: (id, slot) => { - slots.current.set(id, slot); - let items = Array.from(slots.current.values()); - updateState({ slots: items }); - }, - updateSlot: (id, slot) => { - slots.current.set(id, slot); - let items = Array.from(slots.current.values()); - updateState({ slots: items }); - }, - removeSlot: (id) => { - slots.current.set(id, (<>)); - let items = Array.from(slots.current.values()); - updateState({ slots: items }); - }, - clearSlots: () => { - slots.current = new Map(); - let items = Array.from(slots.current.values()); - updateState({ slots: items }); - }, - }; - - return { state, actions }; -} -