mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 20:11:31 +02:00
fix loading bugs in read status service
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
@@ -1,5 +1,7 @@
|
||||
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";
|
||||
@@ -7,7 +9,7 @@ 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;
|
||||
@@ -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" />
|
||||
|
@@ -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);
|
||||
@@ -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 (
|
||||
|
@@ -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";
|
||||
@@ -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];
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
Reference in New Issue
Block a user