Skip to content

Commit

Permalink
Move message observer to ChatEventList (#4441)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Sep 26, 2023
1 parent 636f529 commit 9d0c4a6
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 62 deletions.
61 changes: 57 additions & 4 deletions frontend/app/src/components/home/ChatEventList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ThreadUpdated,
ThreadReactionSelected,
chatIdentifiersEqual,
messageContextsEqual,
} from "openchat-client";
import { menuStore } from "../../stores/menu";
import { tooltipStore } from "../../stores/tooltip";
Expand All @@ -45,6 +46,8 @@
} from "../../stores/scrollPos";
import TimelineDate from "./TimelineDate.svelte";
// todo - these thresholds need to be relative to screen height otherwise things get screwed up on (relatively) tall screens
const MESSAGE_READ_THRESHOLD = 500;
const FROM_BOTTOM_THRESHOLD = 600;
const LOADING_THRESHOLD = 400;
const SCROLL_THRESHOLD = 500;
Expand Down Expand Up @@ -75,10 +78,13 @@
let scrollingToMessage = false;
let scrollToBottomOnSend = false;
let destroyed = false;
let messageObserver: IntersectionObserver;
let labelObserver: IntersectionObserver;
let messageReadTimers: Record<number, number> = {};
$: failedMessagesStore = client.failedMessagesStore;
$: threadSummary = threadRootEvent?.event.thread;
$: messageContext = { chatId: chat.id, threadRootMessageIndex: threadRootEvent?.event.messageIndex };
const keyMeasurements = () => ({
scrollHeight: messagesDiv!.scrollHeight,
Expand Down Expand Up @@ -142,15 +148,61 @@
});
afterUpdate(() => {
showGoToBottom = fromBottom() > FROM_BOTTOM_THRESHOLD;
updateShowGoToBottom();
});
function elementIsOffTheTop(el: Element): boolean {
return el.getBoundingClientRect().top < 95;
}
function updateShowGoToBottom() {
showGoToBottom = fromBottom() > FROM_BOTTOM_THRESHOLD;
}
onMount(() => {
const options = {
const messageObserverOptions = {
root: messagesDiv as Element,
rootMargin: "0px",
threshold: [0.1, 0.2, 0.3, 0.4, 0.5],
};
messageObserver = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
const idxAttrs = entry.target.attributes.getNamedItem("data-index");
const idAttr = entry.target.attributes.getNamedItem("data-id");
const idx = idxAttrs
? Math.max(...idxAttrs.value.split(" ").map((v) => parseInt(v, 10)))
: undefined;
const id = idAttr ? BigInt(idAttr.value) : undefined;
if (idx !== undefined) {
const intersectionRatioRequired =
0 < messagesDivHeight && messagesDivHeight < entry.boundingClientRect.height
? (messagesDivHeight * 0.5) / entry.boundingClientRect.height
: 0.5;
const isIntersecting = entry.intersectionRatio >= intersectionRatioRequired;
if (isIntersecting && messageReadTimers[idx] === undefined) {
const context = messageContext;
const timer = window.setTimeout(() => {
if (messageContextsEqual(context, messageContext)) {
client.markMessageRead(messageContext, idx, id);
if (id !== undefined) {
client.broadcastMessageRead(chat, id);
}
}
delete messageReadTimers[idx];
}, MESSAGE_READ_THRESHOLD);
messageReadTimers[idx] = timer;
}
if (!isIntersecting && messageReadTimers[idx] !== undefined) {
window.clearTimeout(messageReadTimers[idx]);
delete messageReadTimers[idx];
}
}
});
}, messageObserverOptions);
const labelObserverOptions = {
root: messagesDiv as Element,
rootMargin: "-15px 0px 0px 0px",
threshold: [0, 0.5, 1],
Expand All @@ -176,7 +228,7 @@
(label as HTMLElement).style.opacity = "1";
}
}
}, options);
}, labelObserverOptions);
if (messagesDiv !== undefined && $eventListScrollTop !== undefined && maintainScroll) {
interruptScroll(() => {
Expand Down Expand Up @@ -551,6 +603,7 @@
if (maintainScroll) {
$eventListScrollTop = messagesDiv?.scrollTop;
}
updateShowGoToBottom();
menuStore.hideMenu();
tooltipStore.hide();
eventListLastScrolled.set(Date.now());
Expand Down Expand Up @@ -593,7 +646,7 @@
class:interrupt
class:reverse={reverseScroll}
class={`scrollable-list ${rootSelector}`}>
<slot {labelObserver} />
<slot {messageObserver} {labelObserver} />
</div>

{#if !readonly}
Expand Down
57 changes: 4 additions & 53 deletions frontend/app/src/components/home/CurrentChatMessages.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<svelte:options immutable />

<script lang="ts">
import { createEventDispatcher, getContext, onMount } from "svelte";
import { createEventDispatcher, getContext } from "svelte";
import Avatar from "../Avatar.svelte";
import ChatEvent from "./ChatEvent.svelte";
import Robot from "../Robot.svelte";
import ProposalBot from "../ProposalBot.svelte";
import { _ } from "svelte-i18n";
import {
AvatarSize,
type EventWrapper,
Expand All @@ -30,9 +29,6 @@
import TimelineDate from "./TimelineDate.svelte";
import { reverseScroll } from "../../stores/scrollPos";
// todo - these thresholds need to be relative to screen height otherwise things get screwed up on (relatively) tall screens
const MESSAGE_READ_THRESHOLD = 500;
const client = getContext<OpenChat>("client");
const user = client.user;
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -75,52 +71,6 @@
let messagesDivHeight: number;
let initialised = false;
let currentChatId: ChatIdentifier | undefined;
let observer: IntersectionObserver;
let messageReadTimers: Record<number, number> = {};
onMount(() => {
const options = {
root: messagesDiv as Element,
rootMargin: "0px",
threshold: [0.1, 0.2, 0.3, 0.4, 0.5],
};
observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
const idxAttrs = entry.target.attributes.getNamedItem("data-index");
const idAttr = entry.target.attributes.getNamedItem("data-id");
const idx = idxAttrs
? Math.max(...idxAttrs.value.split(" ").map((v) => parseInt(v, 10)))
: undefined;
const id = idAttr ? BigInt(idAttr.value) : undefined;
if (idx !== undefined) {
const intersectionRatioRequired =
0 < messagesDivHeight && messagesDivHeight < entry.boundingClientRect.height
? (messagesDivHeight * 0.5) / entry.boundingClientRect.height
: 0.5;
const isIntersecting = entry.intersectionRatio >= intersectionRatioRequired;
if (isIntersecting && messageReadTimers[idx] === undefined) {
const chatId = chat.id;
const timer = window.setTimeout(() => {
if (chatIdentifiersEqual(chatId, chat.id)) {
client.markMessageRead(chat.id, idx, id);
if (id !== undefined) {
client.broadcastMessageRead(chat, id);
}
}
delete messageReadTimers[idx];
}, MESSAGE_READ_THRESHOLD);
messageReadTimers[idx] = timer;
}
if (!isIntersecting && messageReadTimers[idx] !== undefined) {
window.clearTimeout(messageReadTimers[idx]);
delete messageReadTimers[idx];
}
}
});
}, options);
});
function goToMessageIndex(ev: CustomEvent<{ index: number }>) {
doGoToMessageIndex(ev.detail.index);
Expand Down Expand Up @@ -265,7 +215,7 @@
let messageId = evt.event.kind === "message" ? evt.event.messageId : undefined;
const isRead = client.isMessageRead(chat.id, messageIndex, messageId);
if (!isRead && evt.event.kind === "message" && evt.event.sender === user.userId) {
client.markMessageRead(chat.id, messageIndex, messageId);
client.markMessageRead({ chatId: chat.id }, messageIndex, messageId);
return true;
}
return isRead;
Expand Down Expand Up @@ -367,6 +317,7 @@
bind:initialised
bind:messagesDiv
bind:messagesDivHeight
let:messageObserver
let:labelObserver>
{#if !reverseScroll}
{#if showAvatar}
Expand Down Expand Up @@ -396,7 +347,7 @@
{#each timelineItem.group as innerGroup (userGroupKey(innerGroup))}
{#each innerGroup as evt, i (eventKey(evt))}
<ChatEvent
{observer}
observer={messageObserver}
focused={evt.event.kind === "message" &&
evt.event.messageIndex === $focusMessageIndex &&
!isFailed($failedMessagesStore, evt)}
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/src/components/home/thread/Thread.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
export let chat: ChatSummary;
let chatEventList: ChatEventList | undefined;
let observer: IntersectionObserver = new IntersectionObserver(() => {});
let pollBuilder: PollBuilder;
let giphySelector: GiphySelector;
let memeBuilder: MemeBuilder;
Expand Down Expand Up @@ -89,7 +88,7 @@
$: atRoot = $threadEvents.length === 0 || $threadEvents[0]?.index === 0;
$: events = atRoot ? [rootEvent, ...$threadEvents] : $threadEvents;
$: timeline = client.groupEvents(
reverseScroll ? [...events.reverse()] : events,
reverseScroll ? [...events].reverse() : events,
user.userId,
$expandedDeletedMessages,
reverseScroll
Expand Down Expand Up @@ -363,6 +362,7 @@
bind:initialised
bind:messagesDiv
bind:messagesDivHeight
let:messageObserver
let:labelObserver>
{#if loading}
<Loading />
Expand All @@ -385,7 +385,7 @@
failed={isFailed($failedMessagesStore, evt)}
readByThem
readByMe
{observer}
observer={messageObserver}
focused={evt.event.kind === "message" &&
$focusMessageIndex === evt.event.messageIndex}
{readonly}
Expand Down
4 changes: 2 additions & 2 deletions frontend/openchat-client/src/openchat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,11 +776,11 @@ export class OpenChat extends OpenChatAgentWorker {
}

markMessageRead(
chatId: ChatIdentifier,
context: MessageContext,
messageIndex: number,
messageId: bigint | undefined,
): void {
this.messagesRead.markMessageRead({ chatId }, messageIndex, messageId);
this.messagesRead.markMessageRead(context, messageIndex, messageId);
}

markPinnedMessagesRead(chatId: ChatIdentifier, dateLastPinned: bigint): void {
Expand Down

0 comments on commit 9d0c4a6

Please sign in to comment.