feat: improve query message
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
|
||||
@@ -149,78 +149,17 @@ pub async fn login(
|
||||
state: State<'_, Nostr>,
|
||||
handle: tauri::AppHandle,
|
||||
) -> Result<String, String> {
|
||||
let app = handle.app_handle().clone();
|
||||
let client = &state.client;
|
||||
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||
let hex = public_key.to_hex();
|
||||
let keyring = Entry::new(&id, "nostr_secret").expect("Unexpected.");
|
||||
|
||||
let password = match keyring.get_password() {
|
||||
Ok(pw) => pw,
|
||||
Err(_) => return Err("Cancelled".into()),
|
||||
};
|
||||
|
||||
match bunker {
|
||||
Some(uri) => {
|
||||
let app_keys =
|
||||
Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
|
||||
match NostrConnectURI::parse(uri) {
|
||||
Ok(bunker_uri) => {
|
||||
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None)
|
||||
.await
|
||||
{
|
||||
Ok(signer) => client.set_signer(Some(signer.into())).await,
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
let signer = NostrSigner::Keys(keys);
|
||||
|
||||
// Update signer
|
||||
client.set_signer(Some(signer)).await;
|
||||
}
|
||||
}
|
||||
|
||||
let hex = public_key.to_hex();
|
||||
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
|
||||
|
||||
if let Ok(events) = client.get_events_of(vec![inbox], None).await {
|
||||
if let Some(event) = events.into_iter().next() {
|
||||
for tag in &event.tags {
|
||||
if let Some(TagStandard::Relay(url)) = tag.as_standardized() {
|
||||
let url = url.to_string();
|
||||
|
||||
if client.add_relay(&url).await.is_ok() {
|
||||
println!("Adding relay {} ...", url);
|
||||
|
||||
if client.connect_relay(&url).await.is_ok() {
|
||||
println!("Connecting relay {} ...", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let window = handle.get_webview_window("main").expect("Window is terminated.");
|
||||
let window = app.get_webview_window("main").expect("Window is terminated.");
|
||||
let state = window.state::<Nostr>();
|
||||
let client = &state.client;
|
||||
|
||||
let old = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
let new = Filter::new().kind(Kind::GiftWrap).pubkey(public_key).limit(0);
|
||||
|
||||
if client.reconcile(old, NegentropyOptions::default()).await.is_ok() {
|
||||
println!("Sync done.")
|
||||
};
|
||||
|
||||
if client.subscribe(vec![new], None).await.is_ok() {
|
||||
println!("Waiting for new message...")
|
||||
};
|
||||
|
||||
client
|
||||
.handle_notifications(|notification| async {
|
||||
if let RelayPoolNotification::Message { message, relay_url } = notification {
|
||||
@@ -265,5 +204,64 @@ pub async fn login(
|
||||
.await
|
||||
});
|
||||
|
||||
let password = match keyring.get_password() {
|
||||
Ok(pw) => pw,
|
||||
Err(_) => return Err("Cancelled".into()),
|
||||
};
|
||||
|
||||
match bunker {
|
||||
Some(uri) => {
|
||||
let app_keys =
|
||||
Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
|
||||
match NostrConnectURI::parse(uri) {
|
||||
Ok(bunker_uri) => {
|
||||
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None)
|
||||
.await
|
||||
{
|
||||
Ok(signer) => client.set_signer(Some(signer.into())).await,
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
let signer = NostrSigner::Keys(keys);
|
||||
|
||||
// Update signer
|
||||
client.set_signer(Some(signer)).await;
|
||||
}
|
||||
}
|
||||
|
||||
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
|
||||
let old = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
let new = Filter::new().kind(Kind::GiftWrap).pubkey(public_key).limit(0);
|
||||
|
||||
let mut relays = Vec::new();
|
||||
|
||||
if let Ok(events) = client.get_events_of(vec![inbox], None).await {
|
||||
if let Some(event) = events.into_iter().next() {
|
||||
for tag in &event.tags {
|
||||
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
|
||||
let url = relay.to_string();
|
||||
if client.add_relay(&url).await.is_ok() {
|
||||
relays.push(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if client.reconcile_with(relays.clone(), old, NegentropyOptions::default()).await.is_ok() {
|
||||
handle.emit("synchronized", ()).unwrap();
|
||||
println!("synchronized");
|
||||
};
|
||||
|
||||
if client.subscribe_to(relays, vec![new], None).await.is_ok() {
|
||||
println!("Waiting for new message...")
|
||||
};
|
||||
|
||||
Ok(hex)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub async fn get_chats(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
|
||||
match client.database().query(vec![filter], Order::Desc).await {
|
||||
match client.get_events_of(vec![filter], None).await {
|
||||
Ok(events) => {
|
||||
let rumors = stream::iter(events)
|
||||
.filter_map(|ev| async move {
|
||||
@@ -51,14 +51,13 @@ pub async fn get_chat_messages(
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let client = &state.client;
|
||||
let database = client.database();
|
||||
let signer = client.signer().await.map_err(|e| e.to_string())?;
|
||||
let receiver_pk = signer.public_key().await.map_err(|e| e.to_string())?;
|
||||
let sender_pk = PublicKey::parse(sender).map_err(|e| e.to_string())?;
|
||||
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkeys(vec![receiver_pk, sender_pk]);
|
||||
|
||||
match database.query(vec![filter], Order::Desc).await {
|
||||
match client.get_events_of(vec![filter], None).await {
|
||||
Ok(events) => {
|
||||
let rumors = stream::iter(events)
|
||||
.filter_map(|ev| async move {
|
||||
@@ -84,14 +83,18 @@ pub async fn get_chat_messages(
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn subscribe_to(id: String, state: State<'_, Nostr>) -> Result<(), String> {
|
||||
pub async fn subscribe_to(
|
||||
id: String,
|
||||
relays: Vec<String>,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<(), String> {
|
||||
let client = &state.client;
|
||||
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key).limit(0);
|
||||
let subscription_id = SubscriptionId::new(&id[..6]);
|
||||
|
||||
if client.subscribe_with_id(subscription_id, vec![filter], None).await.is_ok() {
|
||||
if client.subscribe_with_id_to(relays, subscription_id, vec![filter], None).await.is_ok() {
|
||||
println!("Watching ... {}", id)
|
||||
};
|
||||
|
||||
@@ -126,8 +129,6 @@ pub async fn get_inboxes(id: String, state: State<'_, Nostr>) -> Result<Vec<Stri
|
||||
if let Some(TagStandard::Relay(url)) = tag.as_standardized() {
|
||||
let relay = url.to_string();
|
||||
let _ = client.add_relay(&relay).await;
|
||||
let _ = client.connect_relay(&relay).await;
|
||||
|
||||
relays.push(relay);
|
||||
}
|
||||
}
|
||||
@@ -139,18 +140,6 @@ pub async fn get_inboxes(id: String, state: State<'_, Nostr>) -> Result<Vec<Stri
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn drop_inbox(relays: Vec<String>, state: State<'_, Nostr>) -> Result<(), ()> {
|
||||
let client = &state.client;
|
||||
|
||||
for relay in relays.iter() {
|
||||
let _ = client.disconnect_relay(relay).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn send_message(
|
||||
|
||||
@@ -35,7 +35,6 @@ fn main() {
|
||||
send_message,
|
||||
subscribe_to,
|
||||
unsubscribe,
|
||||
drop_inbox
|
||||
]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -71,9 +70,10 @@ fn main() {
|
||||
|
||||
// Config
|
||||
let opts = Options::new()
|
||||
.automatic_authentication(true)
|
||||
.autoconnect(true)
|
||||
.automatic_authentication(false)
|
||||
.timeout(Duration::from_secs(5))
|
||||
.send_timeout(Some(Duration::from_secs(5)))
|
||||
.send_timeout(Some(Duration::from_secs(50)))
|
||||
.connection_timeout(Some(Duration::from_secs(20)));
|
||||
|
||||
// Setup nostr client
|
||||
@@ -83,11 +83,8 @@ fn main() {
|
||||
};
|
||||
|
||||
// Add bootstrap relay
|
||||
let _ = client.add_relay("wss://relay.damus.io/").await;
|
||||
let _ = client.add_relay("wss://relay.nostr.net/").await;
|
||||
|
||||
// Connect
|
||||
client.connect().await;
|
||||
let _ =
|
||||
client.add_relays(["wss://relay.damus.io/", "wss://relay.nostr.net/"]).await;
|
||||
|
||||
// Create global state
|
||||
app.handle().manage(Nostr { client, contact_list: Mutex::new(vec![]) })
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -79,9 +79,9 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async subscribeTo(id: string) : Promise<Result<null, string>> {
|
||||
async subscribeTo(id: string, relays: string[]) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("subscribe_to", { id }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("subscribe_to", { id, relays }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -94,14 +94,6 @@ try {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async dropInbox(relays: string[]) : Promise<Result<null, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("drop_inbox", { relays }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import updateLocale from "dayjs/plugin/updateLocale";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { commands } from "./commands";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(updateLocale);
|
||||
@@ -72,3 +74,20 @@ export function getReceivers(tags: string[][]) {
|
||||
const p = tags.map((tag) => tag[0] === "p" && tag[1]);
|
||||
return p;
|
||||
}
|
||||
|
||||
export const useRelays = (id: string) =>
|
||||
useQuery({
|
||||
queryKey: ["relays", id],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getInboxes(id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { cn } from "@/commons";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function Spinner({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
const spinner = (
|
||||
return (
|
||||
<span className={cn("block relative opacity-65 size-4", className)}>
|
||||
<span className="spinner-leaf" />
|
||||
<span className="spinner-leaf" />
|
||||
@@ -20,28 +17,4 @@ export function Spinner({
|
||||
<span className="spinner-leaf" />
|
||||
</span>
|
||||
);
|
||||
|
||||
if (children === undefined) return spinner;
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center">
|
||||
<span>
|
||||
{/**
|
||||
* `display: contents` removes the content from the accessibility tree in some browsers,
|
||||
* so we force remove it with `aria-hidden`
|
||||
*/}
|
||||
<span
|
||||
aria-hidden
|
||||
style={{ display: "contents", visibility: "hidden" }}
|
||||
// biome-ignore lint/correctness/noConstantCondition: Workaround to use `inert` until https://github.com/facebook/react/pull/24730 is merged.
|
||||
{...{ inert: true ? "" : undefined }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
<div className="absolute flex items-center justify-center">
|
||||
<span>{spinner}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{user?.profile?.picture ? (
|
||||
<Avatar.Image
|
||||
src={`//wsrv.nl/?url=${user.profile?.picture}&w=200&h=200`}
|
||||
alt={user.pubkey}
|
||||
@@ -28,6 +29,7 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
decoding="async"
|
||||
className="w-full aspect-square object-cover outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||
/>
|
||||
) : null}
|
||||
<Avatar.Fallback>
|
||||
<img
|
||||
src={fallback}
|
||||
|
||||
19
src/icons/coop.tsx
Normal file
19
src/icons/coop.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function CoopIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M0 12C0 5.373 5.373 0 12 0c6.628 0 12 5.373 12 12 0 6.628-5.372 12-12 12H1.426A1.426 1.426 0 010 22.574V12z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ const NewLazyImport = createFileRoute('/new')()
|
||||
const ImportKeyLazyImport = createFileRoute('/import-key')()
|
||||
const CreateAccountLazyImport = createFileRoute('/create-account')()
|
||||
const AccountChatsLazyImport = createFileRoute('/$account/chats')()
|
||||
const AccountChatsNewLazyImport = createFileRoute('/$account/chats/new')()
|
||||
const AccountChatsIdLazyImport = createFileRoute('/$account/chats/$id')()
|
||||
|
||||
// Create/Update Routes
|
||||
@@ -60,6 +61,13 @@ const AccountChatsLazyRoute = AccountChatsLazyImport.update({
|
||||
import('./routes/$account.chats.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const AccountChatsNewLazyRoute = AccountChatsNewLazyImport.update({
|
||||
path: '/new',
|
||||
getParentRoute: () => AccountChatsLazyRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/$account.chats.new.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const AccountChatsIdLazyRoute = AccountChatsIdLazyImport.update({
|
||||
path: '/$id',
|
||||
getParentRoute: () => AccountChatsLazyRoute,
|
||||
@@ -120,6 +128,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AccountChatsIdLazyImport
|
||||
parentRoute: typeof AccountChatsLazyImport
|
||||
}
|
||||
'/$account/chats/new': {
|
||||
id: '/$account/chats/new'
|
||||
path: '/new'
|
||||
fullPath: '/$account/chats/new'
|
||||
preLoaderRoute: typeof AccountChatsNewLazyImport
|
||||
parentRoute: typeof AccountChatsLazyImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +148,7 @@ export const routeTree = rootRoute.addChildren({
|
||||
NostrConnectLazyRoute,
|
||||
AccountChatsLazyRoute: AccountChatsLazyRoute.addChildren({
|
||||
AccountChatsIdLazyRoute,
|
||||
AccountChatsNewLazyRoute,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -170,12 +186,17 @@ export const routeTree = rootRoute.addChildren({
|
||||
"/$account/chats": {
|
||||
"filePath": "$account.chats.lazy.tsx",
|
||||
"children": [
|
||||
"/$account/chats/$id"
|
||||
"/$account/chats/$id",
|
||||
"/$account/chats/new"
|
||||
]
|
||||
},
|
||||
"/$account/chats/$id": {
|
||||
"filePath": "$account.chats.$id.lazy.tsx",
|
||||
"parent": "/$account/chats"
|
||||
},
|
||||
"/$account/chats/new": {
|
||||
"filePath": "$account.chats.new.lazy.tsx",
|
||||
"parent": "/$account/chats"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { commands } from "@/commands";
|
||||
import { cn, getReceivers, time } from "@/commons";
|
||||
import { cn, getReceivers, time, useRelays } from "@/commons";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { ArrowUp, CloudArrowUp, Paperclip } from "@phosphor-icons/react";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
@@ -23,14 +23,17 @@ export const Route = createLazyFileRoute("/$account/chats/$id")({
|
||||
|
||||
function Screen() {
|
||||
const { id } = Route.useParams();
|
||||
const { isLoading, data: relays } = useRelays(id);
|
||||
|
||||
useEffect(() => {
|
||||
commands.subscribeTo(id).then(() => console.log("sub: ", id));
|
||||
if (!isLoading && relays?.length)
|
||||
commands.subscribeTo(id, relays).then(() => console.log("sub: ", id));
|
||||
|
||||
return () => {
|
||||
if (!isLoading && relays?.length)
|
||||
commands.unsubscribe(id).then(() => console.log("unsub: ", id));
|
||||
};
|
||||
}, []);
|
||||
}, [isLoading, relays]);
|
||||
|
||||
return (
|
||||
<div className="size-full flex flex-col">
|
||||
@@ -43,6 +46,7 @@ function Screen() {
|
||||
|
||||
function List() {
|
||||
const { account, id } = Route.useParams();
|
||||
const { isLoading: rl, isError: rE } = useRelays(id);
|
||||
const { isLoading, isError, data } = useQuery({
|
||||
queryKey: ["chats", id],
|
||||
queryFn: async () => {
|
||||
@@ -59,18 +63,20 @@ function List() {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
enabled: !rl && !rE,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(item: NostrEvent) => {
|
||||
(item: NostrEvent, idx: number) => {
|
||||
const self = account === item.pubkey;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
key={idx + item.id}
|
||||
className="flex items-center justify-between gap-3 my-1.5 px-3 border-l-2 border-transparent hover:border-blue-400"
|
||||
>
|
||||
<div
|
||||
@@ -139,12 +145,21 @@ function List() {
|
||||
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end [&>div]:min-h-full"
|
||||
>
|
||||
<Virtualizer scrollRef={ref} shift>
|
||||
{isLoading ? (
|
||||
<p>Loading...</p>
|
||||
) : isError || !data ? (
|
||||
<p>Error</p>
|
||||
{isLoading || !data ? (
|
||||
<div className="w-full h-56 flex items-center justify-center">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Spinner />
|
||||
Loading message...
|
||||
</div>
|
||||
</div>
|
||||
) : isError ? (
|
||||
<div className="w-full h-56 flex items-center justify-center">
|
||||
<div className="flex items-center gap-1.5">
|
||||
Cannot load message. Please try again later.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
data.map((item, idx) => renderItem(item, idx))
|
||||
)}
|
||||
</Virtualizer>
|
||||
</ScrollArea.Viewport>
|
||||
@@ -161,25 +176,7 @@ function List() {
|
||||
|
||||
function Form() {
|
||||
const { id } = Route.useParams();
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
data: relays,
|
||||
} = useQuery({
|
||||
queryKey: ["inboxes", id],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getInboxes(id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
const { isLoading, isError, data: relays } = useRelays(id);
|
||||
|
||||
const [newMessage, setNewMessage] = useState("");
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
@@ -80,14 +80,27 @@ function ChatList() {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen("synchronized", async () => {
|
||||
await queryClient.refetchQueries({ queryKey: ["chats"] });
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen<Payload>("event", async (data) => {
|
||||
const event: NostrEvent = JSON.parse(data.payload.event);
|
||||
const chats: NostrEvent[] = await queryClient.getQueryData(["chats"]);
|
||||
|
||||
if (chats) {
|
||||
const exist = chats.find((ev) => ev.pubkey === event.pubkey);
|
||||
|
||||
if (!exist) {
|
||||
@@ -98,10 +111,10 @@ function ChatList() {
|
||||
if (event.pubkey === account) return;
|
||||
|
||||
return [event, ...prevEvents];
|
||||
// queryClient.invalidateQueries(['chats', id]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
||||
14
src/routes/$account.chats.new.lazy.tsx
Normal file
14
src/routes/$account.chats.new.lazy.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { CoopIcon } from "@/icons/coop";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/chats/new")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
return (
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<CoopIcon className="size-10 text-neutral-200 dark:text-neutral-800" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ function Screen() {
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending}
|
||||
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Continue"}
|
||||
</button>
|
||||
|
||||
@@ -90,7 +90,7 @@ function Screen() {
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending}
|
||||
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Continue"}
|
||||
</button>
|
||||
|
||||
@@ -49,7 +49,7 @@ function Screen() {
|
||||
|
||||
if (res.status === "ok") {
|
||||
navigate({
|
||||
to: "/$account/chats",
|
||||
to: "/$account/chats/new",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
|
||||
@@ -16,13 +16,13 @@ function Screen() {
|
||||
<div className="flex flex-col gap-3">
|
||||
<Link
|
||||
to="/create-account"
|
||||
className="w-full h-10 bg-blue-500 hover:bg-blue-600 text-white rounded-lg inline-flex items-center justify-center shadow"
|
||||
className="w-full h-9 bg-blue-500 hover:bg-blue-600 text-white rounded-lg inline-flex items-center justify-center shadow"
|
||||
>
|
||||
Create a new identity
|
||||
</Link>
|
||||
<Link
|
||||
to="/nostr-connect"
|
||||
className="w-full h-10 bg-white hover:bg-neutral-100 dark:hover:bg-neutral-950 dark:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
|
||||
className="w-full h-9 bg-white hover:bg-neutral-100 dark:hover:bg-neutral-950 dark:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
|
||||
>
|
||||
Login with Nostr Connect
|
||||
</Link>
|
||||
|
||||
@@ -76,7 +76,7 @@ function Screen() {
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending}
|
||||
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? <Spinner /> : "Continue"}
|
||||
</button>
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
@@ -36,5 +34,5 @@
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user