Skip to content

Commit

Permalink
fix loading bugs in read status service
Browse files Browse the repository at this point in the history
  • Loading branch information
hzrd149 committed Aug 31, 2024
1 parent 12f984e commit 50dbdcb
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 49 deletions.
32 changes: 20 additions & 12 deletions src/services/read-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class ReadStatusService {
if (ttl) this.setTTL(key, ttl);
else this.setTTL(key, dayjs().add(1, "day").unix());

if (subject.value === undefined && !this.queue.has(key)) {
this.queue.add(key);
if (subject.value === undefined && !this.readQueue.has(key)) {
this.readQueue.add(key);
this.throttleRead();
}

Expand All @@ -37,51 +37,60 @@ class ReadStatusService {
else this.setTTL(key, dayjs().add(1, "day").unix());

this.status.get(key).next(read);
this.writeQueue.add(key);
this.throttleWrite();
}

queue = new Set<string>();
private throttleRead = _throttle(this.read.bind(this), 1000);
private readQueue = new Set<string>();
private throttleRead = _throttle(this.read.bind(this), 100);
async read() {
if (this.queue.size === 0) return;
if (this.readQueue.size === 0) return;

const trans = db.transaction("read");

this.log(`Loading ${this.queue.size} from database`);
this.log(`Loading ${this.readQueue.size} from database`);

await Promise.all(
Array.from(this.queue).map(async (key) => {
Array.from(this.readQueue).map(async (key) => {
this.readQueue.delete(key);
const subject = this.status.get(key);
const status = await trans.store.get(key);

this.log(key, status);

if (status) {
subject.next(status.read);
if (status.ttl) this.setTTL(key, status.ttl);
} else subject.next(false);
}),
);
this.queue.clear();
}

throttleWrite = _throttle(this.write.bind(this), 1000);
private writeQueue = new Set<string>();
private throttleWrite = _throttle(this.write.bind(this), 100);
async write() {
if (this.writeQueue.size === 0) return;

const trans = db.transaction("read", "readwrite");

let count = 0;
const defaultTTL = dayjs().add(1, "day").unix();
for (const [key, subject] of this.status) {
for (const key of this.writeQueue) {
const subject = this.status.get(key);
if (subject.value !== undefined) {
trans.store.put({ key, read: subject.value, ttl: this.ttl.get(key) ?? defaultTTL });
count++;
}
}

this.writeQueue.clear();
await trans.done;

this.log(`Wrote ${count} to database`);
}

async prune() {
const expired = await db.getAllKeysFromIndex("read", "ttl", IDBKeyRange.lowerBound(dayjs().unix(), true));
const expired = await db.getAllKeysFromIndex("read", "ttl", IDBKeyRange.upperBound(dayjs().unix()));

if (expired.length === 0) return;

Expand All @@ -95,7 +104,6 @@ class ReadStatusService {

const readStatusService = new ReadStatusService();

setInterval(readStatusService.write.bind(readStatusService), 10_000);
setInterval(readStatusService.prune.bind(readStatusService), 30_000);

if (import.meta.env.DEV) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Box, Flex, Spacer, Text, useColorModeValue } from "@chakra-ui/react";
import { PropsWithChildren, ReactNode, forwardRef, memo, useCallback, useContext, useEffect } from "react";
import { Box, Flex, Spacer, Text, useColorModeValue } from "@chakra-ui/react";
import dayjs from "dayjs";

import UserAvatar from "../../../components/user/user-avatar";
import Timestamp from "../../../components/timestamp";
import UserName from "../../../components/user/user-name";
import { CheckIcon } from "../../../components/icons";
import FocusedContext from "../focused-context";
import useReadStatus from "../../../hooks/use-read-status";

const ONE_MONTH = 60 * 60 * 24 * 30;
const ONE_MONTH = dayjs().add(1, "month").unix();

type NotificationIconEntryProps = PropsWithChildren<{
icon: ReactNode;
Expand Down Expand Up @@ -54,7 +56,7 @@ const NotificationIconEntry = memo(
onFocus={onClick ? undefined : focusSelf}
onClick={onClick}
userSelect="none"
bg={expanded || !read ? focusColor : undefined}
bg={!read ? focusColor : undefined}
>
<Box>{icon}</Box>
<UserAvatar pubkey={pubkey} size="sm" />
Expand Down
21 changes: 12 additions & 9 deletions src/views/notifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,17 @@ const NotificationsTimeline = memo(
const navigateNextUnread = () => {
const focusedEvent = filteredEvents.find((e) => e.id === focused);

if (focusedEvent) {
const idx = filteredEvents.indexOf(focusedEvent);
for (let i = idx; i < filteredEvents.length; i++) {
if (readStatusService.getStatus(filteredEvents[i].id).value === false) {
setFocus(filteredEvents[i].id);
break;
}
const idx = focusedEvent ? filteredEvents.indexOf(focusedEvent) : 0;
for (let i = idx; i < filteredEvents.length; i++) {
if (readStatusService.getStatus(filteredEvents[i].id).value === false) {
setFocus(filteredEvents[i].id);
break;
}
}
};
const navigateTop = () => setFocus(filteredEvents[0]?.id ?? "");
const navigateEnd = () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? "");

useKeyPressEvent("ArrowUp", navigatePrev);
useKeyPressEvent("ArrowDown", navigateNext);
useKeyPressEvent("ArrowLeft", navigatePrev);
Expand All @@ -121,8 +122,10 @@ const NotificationsTimeline = memo(
useKeyPressEvent("h", navigatePrev);
useKeyPressEvent("j", navigateNext);
useKeyPressEvent("l", navigateNextUnread);
useKeyPressEvent("H", () => setFocus(filteredEvents[0]?.id ?? ""));
useKeyPressEvent("L", () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? ""));
useKeyPressEvent("H", navigateTop);
useKeyPressEvent("Home", navigateTop);
useKeyPressEvent("L", navigateEnd);
useKeyPressEvent("End", navigateEnd);

if (filteredEvents.length === 0)
return (
Expand Down
53 changes: 28 additions & 25 deletions src/views/notifications/threads.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { useNotifications } from "../../providers/global/notifications-provider"
import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents";
import { groupByRoot } from "../../helpers/notification";
import { NostrEvent } from "../../types/nostr-event";
import NotificationIconEntry from "./components/notification-icon-entry";
import { ChevronLeftIcon, ReplyIcon } from "../../components/icons";
import { ChevronLeftIcon } from "../../components/icons";
import { AvatarGroup, Box, Button, ButtonGroup, Flex, LinkBox, Text, useDisclosure } from "@chakra-ui/react";
import UserAvatarLink from "../../components/user/user-avatar-link";
import useSingleEvent from "../../hooks/use-single-event";
Expand All @@ -27,6 +26,7 @@ import { useNavigateInDrawer } from "../../providers/drawer-sub-view-provider";
import useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
import localSettings from "../../services/local-settings";
import GitBranch01 from "../../components/icons/git-branch-01";

const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND];

Expand Down Expand Up @@ -67,29 +67,32 @@ function ThreadGroup({ rootId, events }: { rootId: string; events: NostrEvent[]
const ref = useEventIntersectionRef(events[events.length - 1]);

return (
<NotificationIconEntry icon={<ReplyIcon boxSize={8} />}>
<AvatarGroup size="sm">
{pubkeys.map((pubkey) => (
<UserAvatarLink key={pubkey} pubkey={pubkey} />
<Flex>
<GitBranch01 boxSize={8} color="green.500" mr="2" />
<Flex direction="column" gap="2">
<AvatarGroup size="sm">
{pubkeys.map((pubkey) => (
<UserAvatarLink key={pubkey} pubkey={pubkey} />
))}
</AvatarGroup>
<Box>
<Text fontWeight="bold">
{pubkeys.length > 1 ? pubkeys.length + " people" : pubkeys.length + " person"} replied in thread:
</Text>
{rootEvent && <CompactNoteContent event={rootEvent} maxLength={100} color="GrayText" />}
</Box>
{(events.length > 3 && !showAll.isOpen ? events.slice(0, 3) : events).map((event) => (
<ReplyEntry key={event.id} event={event} />
))}
</AvatarGroup>
<Box>
<Text fontWeight="bold">
{pubkeys.length > 1 ? pubkeys.length + " people" : pubkeys.length + " person"} replied in thread:
</Text>
{rootEvent && <CompactNoteContent event={rootEvent} maxLength={100} color="GrayText" />}
</Box>
{(events.length > 3 && !showAll.isOpen ? events.slice(0, 3) : events).map((event) => (
<ReplyEntry key={event.id} event={event} />
))}
{!showAll.isOpen && events.length > 3 && (
<ButtonGroup>
<Button variant="link" py="2" onClick={showAll.onOpen} colorScheme="primary" fontWeight="bold">
+{events.length - 3} more
</Button>
</ButtonGroup>
)}
</NotificationIconEntry>
{!showAll.isOpen && events.length > 3 && (
<ButtonGroup>
<Button variant="link" py="2" onClick={showAll.onOpen} colorScheme="primary" fontWeight="bold">
+{events.length - 3} more
</Button>
</ButtonGroup>
)}
</Flex>
</Flex>
);
}

Expand Down Expand Up @@ -118,7 +121,7 @@ function ThreadsNotificationsPage() {
<IntersectionObserverProvider callback={callback}>
<VerticalPageLayout>
<Flex gap="2">
<Button leftIcon={<ChevronLeftIcon />} onClick={() => navigate(-1)}>
<Button leftIcon={<ChevronLeftIcon boxSize={6} />} onClick={() => navigate(-1)}>
Back
</Button>
<PeopleListSelection />
Expand Down

0 comments on commit 50dbdcb

Please sign in to comment.