mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-27 20:17:05 +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);
|
if (ttl) this.setTTL(key, ttl);
|
||||||
else this.setTTL(key, dayjs().add(1, "day").unix());
|
else this.setTTL(key, dayjs().add(1, "day").unix());
|
||||||
|
|
||||||
if (subject.value === undefined && !this.queue.has(key)) {
|
if (subject.value === undefined && !this.readQueue.has(key)) {
|
||||||
this.queue.add(key);
|
this.readQueue.add(key);
|
||||||
this.throttleRead();
|
this.throttleRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,51 +37,60 @@ class ReadStatusService {
|
|||||||
else this.setTTL(key, dayjs().add(1, "day").unix());
|
else this.setTTL(key, dayjs().add(1, "day").unix());
|
||||||
|
|
||||||
this.status.get(key).next(read);
|
this.status.get(key).next(read);
|
||||||
|
this.writeQueue.add(key);
|
||||||
this.throttleWrite();
|
this.throttleWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
queue = new Set<string>();
|
private readQueue = new Set<string>();
|
||||||
private throttleRead = _throttle(this.read.bind(this), 1000);
|
private throttleRead = _throttle(this.read.bind(this), 100);
|
||||||
async read() {
|
async read() {
|
||||||
if (this.queue.size === 0) return;
|
if (this.readQueue.size === 0) return;
|
||||||
|
|
||||||
const trans = db.transaction("read");
|
const trans = db.transaction("read");
|
||||||
|
|
||||||
this.log(`Loading ${this.queue.size} from database`);
|
this.log(`Loading ${this.readQueue.size} from database`);
|
||||||
|
|
||||||
await Promise.all(
|
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 subject = this.status.get(key);
|
||||||
const status = await trans.store.get(key);
|
const status = await trans.store.get(key);
|
||||||
|
|
||||||
|
this.log(key, status);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
subject.next(status.read);
|
subject.next(status.read);
|
||||||
if (status.ttl) this.setTTL(key, status.ttl);
|
if (status.ttl) this.setTTL(key, status.ttl);
|
||||||
} else subject.next(false);
|
} 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() {
|
async write() {
|
||||||
|
if (this.writeQueue.size === 0) return;
|
||||||
|
|
||||||
const trans = db.transaction("read", "readwrite");
|
const trans = db.transaction("read", "readwrite");
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const defaultTTL = dayjs().add(1, "day").unix();
|
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) {
|
if (subject.value !== undefined) {
|
||||||
trans.store.put({ key, read: subject.value, ttl: this.ttl.get(key) ?? defaultTTL });
|
trans.store.put({ key, read: subject.value, ttl: this.ttl.get(key) ?? defaultTTL });
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.writeQueue.clear();
|
||||||
await trans.done;
|
await trans.done;
|
||||||
|
|
||||||
this.log(`Wrote ${count} to database`);
|
this.log(`Wrote ${count} to database`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prune() {
|
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;
|
if (expired.length === 0) return;
|
||||||
|
|
||||||
@@ -95,7 +104,6 @@ class ReadStatusService {
|
|||||||
|
|
||||||
const readStatusService = new ReadStatusService();
|
const readStatusService = new ReadStatusService();
|
||||||
|
|
||||||
setInterval(readStatusService.write.bind(readStatusService), 10_000);
|
|
||||||
setInterval(readStatusService.prune.bind(readStatusService), 30_000);
|
setInterval(readStatusService.prune.bind(readStatusService), 30_000);
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
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 { 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 UserAvatar from "../../../components/user/user-avatar";
|
||||||
import Timestamp from "../../../components/timestamp";
|
import Timestamp from "../../../components/timestamp";
|
||||||
import UserName from "../../../components/user/user-name";
|
import UserName from "../../../components/user/user-name";
|
||||||
@@ -7,7 +9,7 @@ import { CheckIcon } from "../../../components/icons";
|
|||||||
import FocusedContext from "../focused-context";
|
import FocusedContext from "../focused-context";
|
||||||
import useReadStatus from "../../../hooks/use-read-status";
|
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<{
|
type NotificationIconEntryProps = PropsWithChildren<{
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
@@ -54,7 +56,7 @@ const NotificationIconEntry = memo(
|
|||||||
onFocus={onClick ? undefined : focusSelf}
|
onFocus={onClick ? undefined : focusSelf}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
bg={expanded || !read ? focusColor : undefined}
|
bg={!read ? focusColor : undefined}
|
||||||
>
|
>
|
||||||
<Box>{icon}</Box>
|
<Box>{icon}</Box>
|
||||||
<UserAvatar pubkey={pubkey} size="sm" />
|
<UserAvatar pubkey={pubkey} size="sm" />
|
||||||
|
@@ -103,16 +103,17 @@ const NotificationsTimeline = memo(
|
|||||||
const navigateNextUnread = () => {
|
const navigateNextUnread = () => {
|
||||||
const focusedEvent = filteredEvents.find((e) => e.id === focused);
|
const focusedEvent = filteredEvents.find((e) => e.id === focused);
|
||||||
|
|
||||||
if (focusedEvent) {
|
const idx = focusedEvent ? filteredEvents.indexOf(focusedEvent) : 0;
|
||||||
const idx = filteredEvents.indexOf(focusedEvent);
|
|
||||||
for (let i = idx; i < filteredEvents.length; i++) {
|
for (let i = idx; i < filteredEvents.length; i++) {
|
||||||
if (readStatusService.getStatus(filteredEvents[i].id).value === false) {
|
if (readStatusService.getStatus(filteredEvents[i].id).value === false) {
|
||||||
setFocus(filteredEvents[i].id);
|
setFocus(filteredEvents[i].id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
const navigateTop = () => setFocus(filteredEvents[0]?.id ?? "");
|
||||||
|
const navigateEnd = () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? "");
|
||||||
|
|
||||||
useKeyPressEvent("ArrowUp", navigatePrev);
|
useKeyPressEvent("ArrowUp", navigatePrev);
|
||||||
useKeyPressEvent("ArrowDown", navigateNext);
|
useKeyPressEvent("ArrowDown", navigateNext);
|
||||||
useKeyPressEvent("ArrowLeft", navigatePrev);
|
useKeyPressEvent("ArrowLeft", navigatePrev);
|
||||||
@@ -121,8 +122,10 @@ const NotificationsTimeline = memo(
|
|||||||
useKeyPressEvent("h", navigatePrev);
|
useKeyPressEvent("h", navigatePrev);
|
||||||
useKeyPressEvent("j", navigateNext);
|
useKeyPressEvent("j", navigateNext);
|
||||||
useKeyPressEvent("l", navigateNextUnread);
|
useKeyPressEvent("l", navigateNextUnread);
|
||||||
useKeyPressEvent("H", () => setFocus(filteredEvents[0]?.id ?? ""));
|
useKeyPressEvent("H", navigateTop);
|
||||||
useKeyPressEvent("L", () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? ""));
|
useKeyPressEvent("Home", navigateTop);
|
||||||
|
useKeyPressEvent("L", navigateEnd);
|
||||||
|
useKeyPressEvent("End", navigateEnd);
|
||||||
|
|
||||||
if (filteredEvents.length === 0)
|
if (filteredEvents.length === 0)
|
||||||
return (
|
return (
|
||||||
|
@@ -12,8 +12,7 @@ import { useNotifications } from "../../providers/global/notifications-provider"
|
|||||||
import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents";
|
import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents";
|
||||||
import { groupByRoot } from "../../helpers/notification";
|
import { groupByRoot } from "../../helpers/notification";
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
import NotificationIconEntry from "./components/notification-icon-entry";
|
import { ChevronLeftIcon } from "../../components/icons";
|
||||||
import { ChevronLeftIcon, ReplyIcon } from "../../components/icons";
|
|
||||||
import { AvatarGroup, Box, Button, ButtonGroup, Flex, LinkBox, Text, useDisclosure } from "@chakra-ui/react";
|
import { AvatarGroup, Box, Button, ButtonGroup, Flex, LinkBox, Text, useDisclosure } from "@chakra-ui/react";
|
||||||
import UserAvatarLink from "../../components/user/user-avatar-link";
|
import UserAvatarLink from "../../components/user/user-avatar-link";
|
||||||
import useSingleEvent from "../../hooks/use-single-event";
|
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 useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
|
||||||
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
|
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
|
||||||
import localSettings from "../../services/local-settings";
|
import localSettings from "../../services/local-settings";
|
||||||
|
import GitBranch01 from "../../components/icons/git-branch-01";
|
||||||
|
|
||||||
const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND];
|
const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND];
|
||||||
|
|
||||||
@@ -67,7 +67,9 @@ function ThreadGroup({ rootId, events }: { rootId: string; events: NostrEvent[]
|
|||||||
const ref = useEventIntersectionRef(events[events.length - 1]);
|
const ref = useEventIntersectionRef(events[events.length - 1]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationIconEntry icon={<ReplyIcon boxSize={8} />}>
|
<Flex>
|
||||||
|
<GitBranch01 boxSize={8} color="green.500" mr="2" />
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
<AvatarGroup size="sm">
|
<AvatarGroup size="sm">
|
||||||
{pubkeys.map((pubkey) => (
|
{pubkeys.map((pubkey) => (
|
||||||
<UserAvatarLink key={pubkey} pubkey={pubkey} />
|
<UserAvatarLink key={pubkey} pubkey={pubkey} />
|
||||||
@@ -89,7 +91,8 @@ function ThreadGroup({ rootId, events }: { rootId: string; events: NostrEvent[]
|
|||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
)}
|
)}
|
||||||
</NotificationIconEntry>
|
</Flex>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +121,7 @@ function ThreadsNotificationsPage() {
|
|||||||
<IntersectionObserverProvider callback={callback}>
|
<IntersectionObserverProvider callback={callback}>
|
||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Button leftIcon={<ChevronLeftIcon />} onClick={() => navigate(-1)}>
|
<Button leftIcon={<ChevronLeftIcon boxSize={6} />} onClick={() => navigate(-1)}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<PeopleListSelection />
|
<PeopleListSelection />
|
||||||
|
Reference in New Issue
Block a user