add infinite scroll "jump to bottom" ui components
This commit is contained in:
parent
f60831454f
commit
29b7c47a7e
@ -0,0 +1,31 @@
|
|||||||
|
@use '../../styles/theme.scss';
|
||||||
|
|
||||||
|
.jump-to-bottom-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.jump-to-bottom {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
font-size: 0.85em;
|
||||||
|
|
||||||
|
left: 0;
|
||||||
|
bottom: 52px;
|
||||||
|
|
||||||
|
padding: 4px 8px 16px 8px;
|
||||||
|
margin-left: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
color: theme.$header-primary;
|
||||||
|
background-color: theme.$background-input-alt;
|
||||||
|
|
||||||
|
.jump {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import React, { MutableRefObject, ReactNode } from 'react';
|
import React, { useMemo, MutableRefObject, ReactNode } from 'react';
|
||||||
|
|
||||||
import { LoadableValueScrolling } from '../require/loadables';
|
import { LoadableValueScrolling } from '../require/loadables';
|
||||||
import { useScrollableCallables } from '../require/react-helper';
|
import { useScrollableCallables } from '../require/react-helper';
|
||||||
@ -18,24 +18,44 @@ function InfiniteScrollRecoil<T>(props: InfiniteScrollRecoilProps<T[], T>) {
|
|||||||
|
|
||||||
const { fetchAboveCallable, fetchBelowCallable, onScrollCallable } = useScrollableCallables(scrollable, 600); // activate fetch above/below when 600 client px from the top/bottom
|
const { fetchAboveCallable, fetchBelowCallable, onScrollCallable } = useScrollableCallables(scrollable, 600); // activate fetch above/below when 600 client px from the top/bottom
|
||||||
|
|
||||||
|
const jumpToBottom = useMemo(() => {
|
||||||
|
if (!scrollable.below?.hasMore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="jump-to-bottom-wrapper">
|
||||||
|
<div className="jump-to-bottom">
|
||||||
|
<div className="text">You are viewing older messages</div>
|
||||||
|
<div className="jump" onClick={async () => { console.log('jump to bottom'); }}>
|
||||||
|
Jump to Bottom
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [scrollable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={infiniteScrollRef} className="infinite-scroll-scroll-base" onScroll={onScrollCallable}>
|
<div>
|
||||||
<div className="infinite-scroll-elements">
|
<div ref={infiniteScrollRef} className="infinite-scroll-scroll-base" onScroll={onScrollCallable}>
|
||||||
<Retry error={scrollable?.above?.error} text={aboveErrorMessage} retryFunc={fetchAboveCallable} />
|
<div className="infinite-scroll-elements">
|
||||||
{children}
|
<Retry error={scrollable.above?.error} text={aboveErrorMessage} retryFunc={fetchAboveCallable} />
|
||||||
<Retry error={scrollable?.below?.error} text={belowErrorMessage} retryFunc={fetchBelowCallable} />
|
{children}
|
||||||
<Retry
|
<Retry error={scrollable.below?.error} text={belowErrorMessage} retryFunc={fetchBelowCallable} />
|
||||||
error={scrollable.error}
|
<Retry
|
||||||
text={initialErrorMessage}
|
error={scrollable.error}
|
||||||
retryFunc={
|
text={initialErrorMessage}
|
||||||
scrollable.retry ??
|
// TODO: Allow null instead of noop func to prevent re-renders
|
||||||
(async () => {
|
retryFunc={
|
||||||
/* do nothing if we are unloaded */
|
scrollable.retry ??
|
||||||
})
|
(async () => {
|
||||||
}
|
/* do nothing if we are unloaded */
|
||||||
/>
|
})
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{jumpToBottom}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ export function useScrollableCallables<T>(
|
|||||||
const distToTop = -(clientHeight - scrollHeight - scrollTop); // keep in mind scrollTop is negative >:]
|
const distToTop = -(clientHeight - scrollHeight - scrollTop); // keep in mind scrollTop is negative >:]
|
||||||
const distToBottom = -scrollTop;
|
const distToBottom = -scrollTop;
|
||||||
|
|
||||||
//lOG.debug(`scroll callable update. to top: ${distToTop}, to bottom: ${distToBottom}`)
|
// lOG.debug(`scroll callable update. to top: ${distToTop}, to bottom: ${distToBottom}`)
|
||||||
|
|
||||||
if (distToTop < threshold && isLoaded(scrollable) && !isEndFailed(scrollable.above)) {
|
if (distToTop < threshold && isLoaded(scrollable) && !isEndFailed(scrollable.above)) {
|
||||||
await fetchAboveCallable();
|
await fetchAboveCallable();
|
||||||
|
@ -392,7 +392,7 @@ export default class CombinedGuild
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
async fetchMessagesAfter(channelId: string, messageOrderId: string, number: number): Promise<Message[]> {
|
async fetchMessagesAfter(channelId: string, messageOrderId: string, number: number): Promise<Message[]> {
|
||||||
Util.failSometimes(0.75); // for testing
|
// xUtil.failSometimes(0.05); // for testing
|
||||||
LOG.debug(`g#${this.id}: fetch messages after ch#${channelId.slice(0, 4)}, mo#${messageOrderId}, ${number}`);
|
LOG.debug(`g#${this.id}: fetch messages after ch#${channelId.slice(0, 4)}, mo#${messageOrderId}, ${number}`);
|
||||||
const members = await this.grabRAMMembersMap();
|
const members = await this.grabRAMMembersMap();
|
||||||
const channels = await this.grabRAMChannelsMap();
|
const channels = await this.grabRAMChannelsMap();
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
@use 'elements-styles/components/buttons.scss';
|
@use 'elements-styles/components/buttons.scss';
|
||||||
@use 'elements-styles/components/file-drop-target.scss';
|
@use 'elements-styles/components/file-drop-target.scss';
|
||||||
|
@use 'elements-styles/components/infinite-scroll.scss';
|
||||||
@use 'elements-styles/components/input-dropdown.scss';
|
@use 'elements-styles/components/input-dropdown.scss';
|
||||||
@use 'elements-styles/components/input-image-edit.scss';
|
@use 'elements-styles/components/input-image-edit.scss';
|
||||||
@use 'elements-styles/components/input-text.scss';
|
@use 'elements-styles/components/input-text.scss';
|
||||||
|
@ -29,6 +29,7 @@ $background-popup-message: rgba(30, 31, 34, 0.75);
|
|||||||
$background-primary-divider: #3f4149;
|
$background-primary-divider: #3f4149;
|
||||||
|
|
||||||
$background-input: #2f3136;
|
$background-input: #2f3136;
|
||||||
|
$background-input-alt: #646872;
|
||||||
$border-input: #1d1e22;
|
$border-input: #1d1e22;
|
||||||
$border-input-hover: #0b0c0e;
|
$border-input-hover: #0b0c0e;
|
||||||
$border-input-focus: #0099ff;
|
$border-input-focus: #0099ff;
|
||||||
|
@ -16,8 +16,12 @@ process.on('unhandledRejection', async (reason, _promise) => {
|
|||||||
const targetGuild = guilds.find(guild => guild.name === 'no chicoms');
|
const targetGuild = guilds.find(guild => guild.name === 'no chicoms');
|
||||||
const members = await DB.getMembers(targetGuild.id);
|
const members = await DB.getMembers(targetGuild.id);
|
||||||
const targetMember = members.find(member => member.display_name === 'Elipzer');
|
const targetMember = members.find(member => member.display_name === 'Elipzer');
|
||||||
|
if (targetMember === undefined) {
|
||||||
|
LOG.error('unable to find target member');
|
||||||
|
}
|
||||||
const channels = await DB.getChannels(targetGuild.id);
|
const channels = await DB.getChannels(targetGuild.id);
|
||||||
const targetChannel = channels.find(channel => channel.name === 'memes');
|
const targetChannel = channels.find(channel => channel.name === 'memes');
|
||||||
|
LOG.debug('inserting testing messages...');
|
||||||
for (let i = 0; i < 2000; ++i) {
|
for (let i = 0; i < 2000; ++i) {
|
||||||
await DB.insertMessage(targetGuild.id, targetChannel.id, targetMember.id, 'Test Message #' + i);
|
await DB.insertMessage(targetGuild.id, targetChannel.id, targetMember.id, 'Test Message #' + i);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user