mirror of
https://github.com/urbit/shrub.git
synced 2024-12-24 20:47:27 +03:00
chat-fe: lazy load chats
This commit is contained in:
parent
360d24e11c
commit
5d7fa0463c
@ -4,7 +4,7 @@ import { Patp, Path, PatpNoSig } from '~/types/noun';
|
||||
import _ from 'lodash';
|
||||
import {makeResource, resourceFromPath} from '../lib/group';
|
||||
import {GroupPolicy, Enc, Post, NodeMap, Content} from '~/types';
|
||||
import { numToUd, unixToDa } from '~/logic/lib/util';
|
||||
import { numToUd, unixToDa, decToUd } from '~/logic/lib/util';
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
parentIndex: string = '',
|
||||
@ -263,6 +263,28 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
async getNewest(ship: string, resource: string, count: number, index = '') {
|
||||
const data = await this.scry<any>('graph-store', `/newest/${ship}/${resource}/${count}${index}`);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
async getOlderSiblings(ship: string, resource: string, count: number, index = '') {
|
||||
const idx = index.split('/').map(decToUd).join('/');
|
||||
const data = await this.scry<any>('graph-store',
|
||||
`/node-siblings/older/${ship}/${resource}/${count}${idx}`
|
||||
);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
async getYoungerSiblings(ship: string, resource: string, count: number, index = '') {
|
||||
const idx = index.split('/').map(decToUd).join('/');
|
||||
const data = await this.scry<any>('graph-store',
|
||||
`/node-siblings/younger/${ship}/${resource}/${count}${idx}`
|
||||
);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
|
||||
getGraphSubset(ship: string, resource: string, start: string, end: string) {
|
||||
return this.scry<any>(
|
||||
'graph-store',
|
||||
|
@ -123,7 +123,9 @@ const addNodes = (json, state) => {
|
||||
if (!('graphs' in state)) { return; }
|
||||
|
||||
let resource = data.resource.ship + '/' + data.resource.name;
|
||||
if (!(resource in state.graphs)) { return; }
|
||||
if (!(resource in state.graphs)) {
|
||||
state.graphs[resource] = new BigIntOrderedMap();
|
||||
}
|
||||
|
||||
for (let i in data.nodes) {
|
||||
let item = data.nodes[i];
|
||||
|
@ -66,7 +66,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
const chatInput = useRef<ChatInput>();
|
||||
|
||||
useEffect(() => {
|
||||
props.api.graph.getGraph(owner,name);
|
||||
props.api.graph.getNewest(owner, name, 10);
|
||||
}, [station]);
|
||||
|
||||
const onFileDrag = useCallback(
|
||||
|
@ -58,29 +58,6 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
});
|
||||
}
|
||||
|
||||
getLetterType(letter) {
|
||||
if (letter.startsWith('/me ')) {
|
||||
letter = letter.slice(4);
|
||||
// remove insignificant leading whitespace.
|
||||
// aces might be relevant to style.
|
||||
while (letter[0] === '\n') {
|
||||
letter = letter.slice(1);
|
||||
}
|
||||
|
||||
return {
|
||||
me: letter
|
||||
};
|
||||
} else if (isUrl(letter)) {
|
||||
return {
|
||||
url: letter
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
text: letter
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
submit(text) {
|
||||
const { props, state } = this;
|
||||
const [,,ship,name] = props.station.split('/');
|
||||
|
@ -58,6 +58,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
private virtualList: VirtualScroller | null;
|
||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
private prevSize = 0;
|
||||
private loadedNewest = false;
|
||||
private loadedOldest = false;
|
||||
|
||||
INITIALIZATION_MAX_TIME = 1500;
|
||||
|
||||
@ -80,12 +82,12 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
this.virtualList = null;
|
||||
this.unreadMarkerRef = React.createRef();
|
||||
this.prevSize = props.graph.size;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('blur', this.handleWindowBlur);
|
||||
window.addEventListener('focus', this.handleWindowFocus);
|
||||
this.initialFetch();
|
||||
setTimeout(() => {
|
||||
if(this.props.scrollTo) {
|
||||
this.scrollToUnread();
|
||||
@ -111,26 +113,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
}
|
||||
|
||||
initialFetch() {
|
||||
/*
|
||||
const { envelopes, mailboxSize, unreadCount } = this.props;
|
||||
if (envelopes.length > 0) {
|
||||
const start = Math.min(mailboxSize - unreadCount, mailboxSize - DEFAULT_BACKLOG_SIZE);
|
||||
this.stayLockedIfActive();
|
||||
this.fetchMessages(start, start + DEFAULT_BACKLOG_SIZE, true).then(() => {
|
||||
if (!this.virtualList) return;
|
||||
this.setState({ idle: false });
|
||||
this.setState({ initialized: true });
|
||||
this.dismissIfLineVisible();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.initialFetch();
|
||||
}, 2000);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ChatWindowProps, prevState) {
|
||||
const { isChatMissing, history, graph, unreadCount, station } = this.props;
|
||||
|
||||
@ -151,9 +133,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
});
|
||||
}*/
|
||||
|
||||
console.log(graph.size);
|
||||
console.log(prevProps.graph.size);
|
||||
|
||||
if(this.prevSize !== graph.size) {
|
||||
this.prevSize = graph.size;
|
||||
this.virtualList?.calculateVisibleItems();
|
||||
@ -199,29 +178,34 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
//this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
|
||||
}
|
||||
|
||||
fetchMessages(start, end, force = false): Promise<void> {
|
||||
start = Math.max(start, 0);
|
||||
end = Math.max(end, 0);
|
||||
const { api, mailboxSize, station } = this.props;
|
||||
async fetchMessages(newer: boolean, force = false): Promise<void> {
|
||||
const { api, station, graph } = this.props;
|
||||
|
||||
if (
|
||||
(this.state.fetchPending ||
|
||||
mailboxSize <= 0)
|
||||
&& !force
|
||||
) {
|
||||
return new Promise((resolve, reject) => {});
|
||||
if ( this.state.fetchPending && !force) {
|
||||
return new Promise((resolve, reject) => {});
|
||||
}
|
||||
|
||||
this.setState({ fetchPending: true });
|
||||
|
||||
start = Math.min(mailboxSize - start, mailboxSize);
|
||||
end = Math.max(mailboxSize - end, 0, start - MAX_BACKLOG_SIZE);
|
||||
const [,, ship, name] = station.split('/');
|
||||
const currSize = graph.size;
|
||||
if(newer && !this.loadedNewest) {
|
||||
const [index] = graph.peekLargest()!;
|
||||
await api.graph.getYoungerSiblings(ship,name, 5, `/${index.toString()}`)
|
||||
if(currSize === graph.size) {
|
||||
console.log('loaded all newest');
|
||||
this.loadedNewest = true;
|
||||
}
|
||||
} else if(!newer && !this.loadedOldest) {
|
||||
const [index] = graph.peekSmallest()!;
|
||||
await api.graph.getOlderSiblings(ship,name, 5, `/${index.toString()}`)
|
||||
if(currSize === graph.size) {
|
||||
console.log('loaded all oldest');
|
||||
this.loadedOldest = true;
|
||||
}
|
||||
}
|
||||
this.setState({ fetchPending: false });
|
||||
|
||||
return api.chat
|
||||
.fetchMessages(end, start, station)
|
||||
.finally(() => {
|
||||
this.setState({ fetchPending: false });
|
||||
});
|
||||
}
|
||||
|
||||
onScroll({ scrollTop, scrollHeight, windowHeight }) {
|
||||
@ -319,8 +303,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
/>
|
||||
);
|
||||
}}
|
||||
loadRows={(start, end) => {
|
||||
this.fetchMessages(start.toJSNumber(), end.toJSNumber());
|
||||
loadRows={(newer) => {
|
||||
this.fetchMessages(newer);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -28,6 +28,9 @@ const renderers = {
|
||||
inlineCode: ({language, value}) => {
|
||||
return <Text mono p='1' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
||||
},
|
||||
paragraph: ({ children }) => {
|
||||
return (<Text fontSize="14px">{children}</Text>);
|
||||
},
|
||||
code: ({language, value}) => {
|
||||
return <Text
|
||||
p='1'
|
||||
|
@ -14,7 +14,7 @@ interface RendererProps {
|
||||
|
||||
interface VirtualScrollerProps {
|
||||
origin: 'top' | 'bottom';
|
||||
loadRows(start: BigInteger, end: BigInteger): void;
|
||||
loadRows(newer: boolean): void;
|
||||
data: BigIntOrderedMap<BigInteger, any>;
|
||||
renderer: (props: RendererProps) => JSX.Element | null;
|
||||
onStartReached?(): void;
|
||||
@ -69,7 +69,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
|
||||
this.setScrollTop = this.setScrollTop.bind(this);
|
||||
this.scrollToData = this.scrollToData.bind(this);
|
||||
this.scrollKeyMap = this.scrollKeyMap.bind(this);
|
||||
this.loadRows = _.memoize(this.loadRows).bind(this);
|
||||
this.loadRows = _.debounce(this.loadRows, 300, { leading: true }).bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -143,14 +143,9 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
|
||||
const { data, size: totalSize, onCalculateVisibleItems } = this.props;
|
||||
|
||||
|
||||
//console.log([...items].map(([index]) => this.heightOf(index)));
|
||||
//const list = [...data];
|
||||
//console.log(list[0][0].toString());
|
||||
// console.log(list[list.length - 1][0].toString());
|
||||
[...data].forEach(([index, datum]) => {
|
||||
const height = this.heightOf(index);
|
||||
if (startgap < scrollTop && !startGapFilled) {
|
||||
console.log(index.toString());
|
||||
startBuffer.set(index, datum);
|
||||
startgap += height;
|
||||
} else if (heightShown < windowHeight) {
|
||||
@ -164,31 +159,26 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
|
||||
}
|
||||
});
|
||||
|
||||
console.log(startgap);
|
||||
|
||||
|
||||
startBuffer = new BigIntOrderedMap([...startBuffer].reverse().slice(0, (visibleItems.size - visibleItems.size % 5)));
|
||||
startBuffer = new BigIntOrderedMap(
|
||||
[...startBuffer].reverse().slice(0, (visibleItems.size - visibleItems.size % 5))
|
||||
);
|
||||
|
||||
|
||||
startBuffer.forEach((_datum, index) => {
|
||||
startgap -= this.heightOf(index);
|
||||
});
|
||||
|
||||
console.log(startBuffer.size);
|
||||
console.log(startgap);
|
||||
|
||||
const firstVisibleKey = visibleItems.peekLargest()?.[0] ?? this.estimateIndexFromScrollTop(scrollTop)!;
|
||||
const firstNeededKey = bigIntUtils.max(firstVisibleKey.subtract(bigInt(this.OVERSCAN_SIZE)), bigInt.zero)
|
||||
if (!data.has(firstNeededKey.add(bigInt.one))) {
|
||||
this.loadRows(firstNeededKey, firstVisibleKey.subtract(bigInt.one));
|
||||
const firstVisibleKey = visibleItems.peekSmallest()?.[0] ?? this.estimateIndexFromScrollTop(scrollTop)!;
|
||||
if (data.peekSmallest()![0].eq(firstVisibleKey)) {
|
||||
this.loadRows(false);
|
||||
}
|
||||
const lastVisibleKey =
|
||||
visibleItems.peekSmallest()?.[0]
|
||||
visibleItems.peekLargest()?.[0]
|
||||
?? bigInt(this.estimateIndexFromScrollTop(scrollTop + windowHeight)!);
|
||||
const lastNeededKey = bigIntUtils.min(lastVisibleKey.add(bigInt(this.OVERSCAN_SIZE)), bigInt(totalSize));
|
||||
|
||||
if (!data.has(lastNeededKey.subtract(bigInt.one))) {
|
||||
this.loadRows(lastVisibleKey.add(bigInt.one), lastNeededKey);
|
||||
if (data.peekLargest()![0].eq(lastVisibleKey)) {
|
||||
this.loadRows(true);
|
||||
}
|
||||
onCalculateVisibleItems ? onCalculateVisibleItems(visibleItems) : null;
|
||||
this.setState({
|
||||
@ -198,25 +188,8 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
|
||||
});
|
||||
}
|
||||
|
||||
loadRows(start: BigInteger, end: BigInteger) {
|
||||
if (this.pendingLoad?.timeout) {
|
||||
clearTimeout(this.pendingLoad.timeout);
|
||||
start = bigIntUtils.min(start, this.pendingLoad.start);
|
||||
end = bigIntUtils.max(end, this.pendingLoad.end);
|
||||
}
|
||||
this.pendingLoad = {
|
||||
timeout: setTimeout(() => {
|
||||
if (!this.pendingLoad) return;
|
||||
start = bigIntUtils.max(this.pendingLoad.start, bigInt.zero);
|
||||
end = bigIntUtils.min(bigIntUtils.max(this.pendingLoad.end, bigInt.zero), bigInt(this.props.size));
|
||||
if (start < end) {
|
||||
this.props.loadRows(start, end);
|
||||
}
|
||||
clearTimeout(this.pendingLoad.timeout);
|
||||
this.pendingLoad = undefined;
|
||||
}, 500),
|
||||
start, end
|
||||
};
|
||||
loadRows(newer: boolean) {
|
||||
this.props.loadRows(newer);
|
||||
}
|
||||
|
||||
scrollKeyMap(): Map<string, number> {
|
||||
|
Loading…
Reference in New Issue
Block a user