From c791ea802c569766e6387ed41ffa98c54f183c36 Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:23:54 +0700 Subject: [PATCH] feat: update ui --- src-tauri/src/commands/chat.rs | 29 +++++++++++-- src-tauri/src/main.rs | 14 +++++-- src/commands.ts | 12 +++++- src/commons.ts | 6 ++- src/components/user/name.tsx | 12 ++---- src/routes.gen.ts | 18 ++++++++ src/routes/$account.chats.$id.tsx | 66 ++++++++++++++++++++---------- src/routes/$account.chats.lazy.tsx | 66 +++++++++++++++++++++++++----- src/routes/contacts.lazy.tsx | 5 +++ 9 files changed, 178 insertions(+), 50 deletions(-) create mode 100644 src/routes/contacts.lazy.tsx diff --git a/src-tauri/src/commands/chat.rs b/src-tauri/src/commands/chat.rs index 48a19c9..db4a966 100644 --- a/src-tauri/src/commands/chat.rs +++ b/src-tauri/src/commands/chat.rs @@ -54,9 +54,9 @@ pub async fn get_chats(db_only: bool, state: State<'_, Nostr>) -> Result>(); @@ -102,9 +102,17 @@ pub async fn get_chat_messages(id: String, state: State<'_, Nostr>) -> Result) -> Result, String> { +pub async fn connect_inbox(id: String, state: State<'_, Nostr>) -> Result, String> { let client = &state.client; let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?; + let mut inbox_relays = state.inbox_relays.lock().await; + + if let Some(relays) = inbox_relays.get(&public_key) { + for relay in relays { + let _ = client.connect_relay(relay).await; + } + return Ok(relays.to_owned()); + } let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1); @@ -123,7 +131,6 @@ pub async fn get_inboxes(id: String, state: State<'_, Nostr>) -> Result) -> Result) -> Result<(), String> { + let client = &state.client; + let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?; + let inbox_relays = state.inbox_relays.lock().await; + + if let Some(relays) = inbox_relays.get(&public_key) { + for relay in relays { + let _ = client.disconnect_relay(relay).await; + } + } + + Ok(()) +} + #[tauri::command] #[specta::specta] pub async fn send_message( diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 22ff04d..7228dc9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -14,6 +14,7 @@ mod common; pub struct Nostr { client: Client, + contact_list: Mutex>, inbox_relays: Mutex>>, } @@ -28,7 +29,8 @@ fn main() { get_metadata, get_chats, get_chat_messages, - get_inboxes, + connect_inbox, + disconnect_inbox, send_message, ]); @@ -84,7 +86,9 @@ fn main() { let client = ClientBuilder::default().opts(opts).database(database).build(); // Add bootstrap relay - let _ = client.add_relays(["wss://relay.damus.io", "wss://relay.nostr.net"]).await; + let _ = client + .add_relays(["wss://relay.poster.place/", "wss://bostr.nokotaro.com/"]) + .await; // Connect client.connect().await; @@ -93,7 +97,11 @@ fn main() { }); // Create global state - app.manage(Nostr { client, inbox_relays: Mutex::new(HashMap::new()) }); + app.manage(Nostr { + client, + contact_list: Mutex::new(Vec::new()), + inbox_relays: Mutex::new(HashMap::new()), + }); Ok(()) }) diff --git a/src/commands.ts b/src/commands.ts index 38dd7c7..312ded9 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -63,9 +63,17 @@ try { else return { status: "error", error: e as any }; } }, -async getInboxes(id: string) : Promise> { +async connectInbox(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_inboxes", { id }) }; + return { status: "ok", data: await TAURI_INVOKE("connect_inbox", { id }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async disconnectInbox(id: string) : Promise> { +try { + return { status: "ok", data: await TAURI_INVOKE("disconnect_inbox", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; diff --git a/src/commons.ts b/src/commons.ts index 5bab285..853f1de 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -3,6 +3,7 @@ import { type ClassValue, clsx } from "clsx"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import updateLocale from "dayjs/plugin/updateLocale"; +import { nip19 } from "nostr-tools"; import { twMerge } from "tailwind-merge"; import { commands } from "./commands"; @@ -29,6 +30,7 @@ export function cn(...inputs: ClassValue[]) { export function npub(pubkey: string, len: number) { if (pubkey.length <= len) return pubkey; + const npub = pubkey.startsWith("npub1") ? pubkey : nip19.npubEncode(pubkey); const separator = " ... "; const sepLen = separator.length; @@ -37,9 +39,9 @@ export function npub(pubkey: string, len: number) { const backChars = Math.floor(charsToShow / 2); return ( - pubkey.substring(0, frontChars) + + npub.substring(0, frontChars) + separator + - pubkey.substring(pubkey.length - backChars) + npub.substring(npub.length - backChars) ); } diff --git a/src/components/user/name.tsx b/src/components/user/name.tsx index 2394b0c..036136a 100644 --- a/src/components/user/name.tsx +++ b/src/components/user/name.tsx @@ -1,14 +1,8 @@ -import { cn } from "@/commons"; +import { cn, npub } from "@/commons"; import { useUserContext } from "./provider"; -import { useMemo } from "react"; -import { uniqueNamesGenerator, names } from "unique-names-generator"; export function UserName({ className }: { className?: string }) { const user = useUserContext(); - const name = useMemo( - () => uniqueNamesGenerator({ dictionaries: [names] }), - [user.pubkey], - ); if (user.isLoading) { return ( @@ -18,7 +12,9 @@ export function UserName({ className }: { className?: string }) { return (
- {user.profile?.display_name || user.profile?.name || name} + {user.profile?.display_name || + user.profile?.name || + npub(user.pubkey, 16)}
); } diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 09dbf36..679cb28 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -22,6 +22,7 @@ const NostrConnectLazyImport = createFileRoute('/nostr-connect')() const NewLazyImport = createFileRoute('/new')() const ImportKeyLazyImport = createFileRoute('/import-key')() const CreateAccountLazyImport = createFileRoute('/create-account')() +const ContactsLazyImport = createFileRoute('/contacts')() const AccountChatsLazyImport = createFileRoute('/$account/chats')() const AccountChatsNewLazyImport = createFileRoute('/$account/chats/new')() @@ -49,6 +50,11 @@ const CreateAccountLazyRoute = CreateAccountLazyImport.update({ import('./routes/create-account.lazy').then((d) => d.Route), ) +const ContactsLazyRoute = ContactsLazyImport.update({ + path: '/contacts', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/contacts.lazy').then((d) => d.Route)) + const IndexRoute = IndexImport.update({ path: '/', getParentRoute: () => rootRoute, @@ -84,6 +90,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/contacts': { + id: '/contacts' + path: '/contacts' + fullPath: '/contacts' + preLoaderRoute: typeof ContactsLazyImport + parentRoute: typeof rootRoute + } '/create-account': { id: '/create-account' path: '/create-account' @@ -140,6 +153,7 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren({ IndexRoute, + ContactsLazyRoute, CreateAccountLazyRoute, ImportKeyLazyRoute, NewLazyRoute, @@ -159,6 +173,7 @@ export const routeTree = rootRoute.addChildren({ "filePath": "__root.tsx", "children": [ "/", + "/contacts", "/create-account", "/import-key", "/new", @@ -169,6 +184,9 @@ export const routeTree = rootRoute.addChildren({ "/": { "filePath": "index.tsx" }, + "/contacts": { + "filePath": "contacts.lazy.tsx" + }, "/create-account": { "filePath": "create-account.lazy.tsx" }, diff --git a/src/routes/$account.chats.$id.tsx b/src/routes/$account.chats.$id.tsx index 6bef332..2c3df85 100644 --- a/src/routes/$account.chats.$id.tsx +++ b/src/routes/$account.chats.$id.tsx @@ -1,7 +1,8 @@ import { commands } from "@/commands"; import { cn, getReceivers, time } from "@/commons"; import { Spinner } from "@/components/spinner"; -import { ArrowUp, Paperclip } from "@phosphor-icons/react"; +import { User } from "@/components/user"; +import { ArrowUp, DotsThree, Paperclip } from "@phosphor-icons/react"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; @@ -20,11 +21,8 @@ type Payload = { export const Route = createFileRoute("/$account/chats/$id")({ beforeLoad: async ({ params }) => { - const inboxRelays: string[] = await invoke("get_inboxes", { - id: params.id, - }); - - return { inboxRelays }; + const inbox: string[] = await invoke("connect_inbox", { id: params.id }); + return { inbox }; }, component: Screen, pendingComponent: Pending, @@ -41,13 +39,48 @@ function Pending() { function Screen() { return (
-
+
); } +function Header() { + const { account, id } = Route.useParams(); + + return ( +
+
+
+ + + + + + + + + + +
+
+
+
+ + + + +
Connected
+
+
+
+ ); +} + function List() { const { account, id } = Route.useParams(); const { isLoading, isError, data } = useQuery({ @@ -195,17 +228,14 @@ function List() { function Form() { const { id } = Route.useParams(); - const { inboxRelays } = Route.useRouteContext(); + const { inbox } = Route.useRouteContext(); const [newMessage, setNewMessage] = useState(""); const [isPending, startTransition] = useTransition(); - // const queryClient = useQueryClient(); - const submit = async () => { startTransition(async () => { if (!newMessage.length) return; - if (!inboxRelays?.length) return; const res = await commands.sendMessage(id, newMessage); @@ -220,27 +250,19 @@ function Form() { return (
- {!inboxRelays.length ? ( -
+ {!inbox.length ? ( +
This user doesn't have inbox relays. You cannot send messages to them.
) : (
-
+
- {/* -
- -
- */}
@@ -82,7 +83,7 @@ function ChatList() { }); useEffect(() => { - const unlisten = listen("event", async (data) => { + const unlisten = listen("new_chat", async (data) => { const event: NostrEvent = JSON.parse(data.payload.event); const chats: NostrEvent[] = await queryClient.getQueryData(["chats"]); @@ -99,6 +100,17 @@ function ChatList() { return [event, ...prevEvents]; }, ); + } else { + const index = chats.findIndex((item) => item.pubkey === event.pubkey); + const newEvents = [...chats]; + + if (index !== -1) { + newEvents[index] = { + ...event, + }; + + await queryClient.setQueryData(["chats"], newEvents); + } } } }); @@ -142,7 +154,7 @@ function ChatList() { isActive ? "bg-black/5 dark:bg-white/5" : "", )} > - +
@@ -173,16 +185,50 @@ function ChatList() { } function CurrentUser() { - const { account } = Route.useParams(); + const params = Route.useParams(); + const navigate = Route.useNavigate(); + + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "Settings", + action: () => navigate({ to: "/" }), + }), + MenuItem.new({ + text: "Feedback", + action: () => navigate({ to: "/" }), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Switch account", + action: () => navigate({ to: "/" }), + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); return ( -
- +
+ +
); } diff --git a/src/routes/contacts.lazy.tsx b/src/routes/contacts.lazy.tsx new file mode 100644 index 0000000..5204e88 --- /dev/null +++ b/src/routes/contacts.lazy.tsx @@ -0,0 +1,5 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/contacts')({ + component: () =>
Hello /contacts!
+}) \ No newline at end of file