124 lines
3.0 KiB
Svelte
124 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
import FAB from './ui/FAB.svelte';
|
|
import MessageView from './MessageView.svelte';
|
|
import Input from './Input.svelte';
|
|
|
|
import { Svrollbar } from 'svrollbar';
|
|
|
|
import { chat_lock, chat, localMessages as messages, shouldTweet } from './chat';
|
|
import { globalMode } from './settings';
|
|
import { onMount, onDestroy, tick } from 'svelte';
|
|
import type { Unsubscriber } from 'svelte/store';
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/svelte-fontawesome';
|
|
import { faArrowDown } from '@fortawesome/free-solid-svg-icons';
|
|
|
|
let stickToBottom = true;
|
|
|
|
let viewport: Element, contents: Element;
|
|
let tweet: HTMLAudioElement;
|
|
|
|
shouldTweet.subscribe((should) => {
|
|
if (should) {
|
|
if (tweet) {
|
|
tweet.volume = 0.5;
|
|
tweet?.play().catch(() => {});
|
|
}
|
|
shouldTweet.set(false);
|
|
}
|
|
});
|
|
|
|
export async function scrollToBottom(smooth = true) {
|
|
viewport &&
|
|
viewport.scrollTo({
|
|
top: viewport.scrollHeight,
|
|
behavior: smooth ? 'smooth' : undefined
|
|
});
|
|
}
|
|
|
|
let releaseLock: Unsubscriber;
|
|
onMount(async () => {
|
|
releaseLock = chat_lock.subscribe(() => {});
|
|
await chat.hydrate();
|
|
await tick();
|
|
viewport && (viewport.scrollTop = viewport.scrollHeight);
|
|
});
|
|
|
|
messages.subscribe(async () => {
|
|
if (stickToBottom) {
|
|
// Wait for the message to be added to the DOM
|
|
await tick();
|
|
|
|
// Scroll the list to the bottom with the new DOM
|
|
await scrollToBottom(false);
|
|
// vlist?.scrollToIndex(ms.length);
|
|
|
|
// Fake debounce to make sure we're still stuck at the bottom
|
|
await tick();
|
|
stickToBottom = true;
|
|
}
|
|
});
|
|
|
|
onDestroy(() => {
|
|
releaseLock?.();
|
|
});
|
|
|
|
function onScroll(e: Event) {
|
|
if (!e.isTrusted) return;
|
|
const scrollOffset = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
|
|
if (scrollOffset >= 80) {
|
|
stickToBottom = false;
|
|
} else if (scrollOffset < 80) {
|
|
stickToBottom = true;
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<div class="scroller">
|
|
<audio src="/tweet.mp3" bind:this={tweet} />
|
|
<Svrollbar {viewport} {contents} margin={{ right: 6, bottom: 12, top: 12 }}
|
|
alwaysVisible
|
|
--svrollbar-track-width="8px" --svrollbar-thumb-width="4px" />
|
|
<MessageView messages="{$messages}" showChannel="{$globalMode}"
|
|
bind:viewport bind:contents on:scroll={onScroll} />
|
|
{#if !stickToBottom}
|
|
<FAB --background="{'var(--base-700)'}" on:click={() => scrollToBottom()}><FontAwesomeIcon icon="{faArrowDown}" /></FAB>
|
|
{/if}
|
|
</div>
|
|
<Input />
|
|
|
|
<style>
|
|
.scroller {
|
|
min-height: 0;
|
|
flex-grow: 1;
|
|
flex-basis: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.scroller > :global(.viewport) {
|
|
overflow: scroll;
|
|
height: 100% !important;
|
|
}
|
|
|
|
.scroller :global(.contents) {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.scroller > :global(*) {
|
|
/* hide scrollbar */
|
|
-ms-overflow-style: none !important;
|
|
scrollbar-width: none !important;
|
|
}
|
|
|
|
.scroller > :global(*::-webkit-scrollbar) {
|
|
/* hide scrollbar */
|
|
display: none !important;
|
|
}
|
|
|
|
.scroller :global(.v-thumb) {
|
|
z-index: 101;
|
|
}
|
|
</style>
|