feat: improve virtual scroller
This commit is contained in:
@@ -1,11 +1,18 @@
|
|||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use keyring_search::{Limit, List, Search};
|
use keyring_search::{Limit, List, Search};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use serde::Serialize;
|
||||||
use std::{collections::HashSet, time::Duration};
|
use std::{collections::HashSet, time::Duration};
|
||||||
use tauri::State;
|
use tauri::{Emitter, Manager, State};
|
||||||
|
|
||||||
use crate::{Nostr, BOOTSTRAP_RELAYS};
|
use crate::{Nostr, BOOTSTRAP_RELAYS};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
pub struct EventPayload {
|
||||||
|
event: String, // JSON String
|
||||||
|
sender: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_accounts() -> Vec<String> {
|
pub fn get_accounts() -> Vec<String> {
|
||||||
@@ -207,6 +214,7 @@ pub async fn login(
|
|||||||
id: String,
|
id: String,
|
||||||
bunker: Option<String>,
|
bunker: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
|
handle: tauri::AppHandle,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
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
|
// Resubscribe new message for current user
|
||||||
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
|
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
|
||||||
} else {
|
} 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)
|
Ok(hex)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,15 @@ import { createLazyFileRoute } from "@tanstack/react-router";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import type { NostrEvent } from "nostr-tools";
|
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 { useEffect } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer, type VirtualizerHandle } from "virtua";
|
||||||
|
|
||||||
type ChatPayload = {
|
type ChatPayload = {
|
||||||
events: string[];
|
events: string[];
|
||||||
@@ -104,7 +110,10 @@ function List() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
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(
|
const renderItem = useCallback(
|
||||||
(item: NostrEvent, idx: number) => {
|
(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 (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
@@ -196,10 +219,20 @@ function List() {
|
|||||||
className="overflow-hidden flex-1 w-full"
|
className="overflow-hidden flex-1 w-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={scrollRef}
|
||||||
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end [&>div]:min-h-full"
|
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 ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between gap-3 my-1.5 px-3">
|
<div className="flex items-center justify-between gap-3 my-1.5 px-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user