waffle/frontend/src/components/Messages.svelte
2022-04-28 18:48:44 +03:00

79 lines
2.7 KiB
Svelte

<script>
import { afterUpdate } from "svelte";
import { messagesStoreProvider, smallViewport } from "../stores.js";
import Message from "./Message.svelte";
export let channelId;
let scrollTarget;
let scrollAnchor;
let shouldAutoscroll = true;
let lastScrollHeight = null;
let isScrolledToBottom = true;
$: messages = messagesStoreProvider.getStore(channelId);
afterUpdate(() => {
// hacky way to preserve scroll position when messages are pushed back
if (lastScrollHeight) {
scrollTarget.scrollTop = scrollTarget.scrollHeight - lastScrollHeight;
lastScrollHeight = null;
return;
}
if (shouldAutoscroll && scrollAnchor) {
scrollAnchor.scrollIntoView(false);
}
});
const onScroll = (e) => {
const { scrollTop, offsetHeight, scrollHeight } = e.target;
if ((scrollTop + offsetHeight) >= scrollHeight) { // user scrolled to bottom
messages.setIsCollectingOldMessages(true);
shouldAutoscroll = true;
isScrolledToBottom = true;
} else {
shouldAutoscroll = false;
isScrolledToBottom = false;
if (scrollTop === 0) {
// load older messages if the user scrolls to the top.
// save the current scroll height if the server returned any messages,
// before commiting them to the store. this is to provide the jank scroll height
// preservation
messages.loadOlderMessages(() => {
if (scrollTarget)
lastScrollHeight = scrollTarget.scrollHeight;
});
}
messages.setIsCollectingOldMessages(false);
}
};
const windowDidResize = () => {
// TODO: hack
// showSidebar is false on small viewports
// scrolling to bottom when the virtual keyboard pops up does not work on chromium purely based on isScrolledToBottom for some reason
if (isScrolledToBottom || $smallViewport) {
scrollAnchor.scrollIntoView(false);
}
};
</script>
<svelte:window on:resize={ windowDidResize } />
<style>
.messages-container {
height: 100%;
width: 100%;
flex-grow: 0;
overflow-y: auto;
overflow-x: hidden;
background-color: var(--background-color-1);
padding-top: var(--space-sm);
}
</style>
<div class="messages-container" on:scroll={ onScroll } bind:this={ scrollTarget }>
{#each $messages as message (message.id)}
<Message message={message} />
{/each}
<div bind:this={ scrollAnchor } />
</div>