feat: improve virtual scroller

This commit is contained in:
reya
2024-08-01 16:31:56 +07:00
parent 8f73e8ccd8
commit e54ca6e62f
2 changed files with 71 additions and 7 deletions

View File

@@ -1,11 +1,18 @@
use keyring::Entry;
use keyring_search::{Limit, List, Search};
use nostr_sdk::prelude::*;
use serde::Serialize;
use std::{collections::HashSet, time::Duration};
use tauri::State;
use tauri::{Emitter, Manager, State};
use crate::{Nostr, BOOTSTRAP_RELAYS};
#[derive(Clone, Serialize)]
pub struct EventPayload {
event: String, // JSON String
sender: String,
}
#[tauri::command]
#[specta::specta]
pub fn get_accounts() -> Vec<String> {
@@ -207,6 +214,7 @@ pub async fn login(
id: String,
bunker: Option<String>,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
let client = &state.client;
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
@@ -281,8 +289,31 @@ pub async fn login(
// Resubscribe new message for current user
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
} else {
let _ = client.subscribe_with_id(sub_id, vec![new_message], None).await;
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
}
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Event { event, subscription_id, .. } = notification {
if subscription_id == sub_id && event.kind == Kind::GiftWrap {
if let Ok(UnwrappedGift { rumor, sender }) =
client.unwrap_gift_wrap(&event).await
{
let payload =
EventPayload { event: rumor.as_json(), sender: sender.to_hex() };
handle.emit("event", payload).unwrap();
}
}
}
Ok(false)
})
.await
});
Ok(hex)
}

View File

@@ -9,9 +9,15 @@ import { createLazyFileRoute } from "@tanstack/react-router";
import { listen } from "@tauri-apps/api/event";
import { message } from "@tauri-apps/plugin-dialog";
import type { NostrEvent } from "nostr-tools";
import { useCallback, useRef, useState, useTransition } from "react";
import {
useCallback,
useLayoutEffect,
useRef,
useState,
useTransition,
} from "react";
import { useEffect } from "react";
import { Virtualizer } from "virtua";
import { Virtualizer, type VirtualizerHandle } from "virtua";
type ChatPayload = {
events: string[];
@@ -104,7 +110,10 @@ function List() {
});
const queryClient = useQueryClient();
const ref = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const ref = useRef<VirtualizerHandle>(null);
const isPrepend = useRef(false);
const shouldStickToBottom = useRef(true);
const renderItem = useCallback(
(item: NostrEvent, idx: number) => {
@@ -189,6 +198,20 @@ function List() {
};
}, []);
useEffect(() => {
if (!data?.length) return;
if (!ref.current) return;
if (!shouldStickToBottom.current) return;
ref.current.scrollToIndex(data.length - 1, {
align: "end",
});
}, [data]);
useLayoutEffect(() => {
isPrepend.current = false;
});
return (
<ScrollArea.Root
type={"scroll"}
@@ -196,10 +219,20 @@ function List() {
className="overflow-hidden flex-1 w-full"
>
<ScrollArea.Viewport
ref={ref}
ref={scrollRef}
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end [&>div]:min-h-full"
>
<Virtualizer scrollRef={ref} shift>
<Virtualizer
scrollRef={scrollRef}
ref={ref}
shift={isPrepend.current}
onScroll={(offset) => {
if (!ref.current) return;
shouldStickToBottom.current =
offset - ref.current.scrollSize + ref.current.viewportSize >=
-1.5;
}}
>
{isLoading ? (
<>
<div className="flex items-center justify-between gap-3 my-1.5 px-3">