diff --git a/src/routes/$account/_layout/chats.lazy.tsx b/src/routes/$account/_layout/chats.lazy.tsx index 681a43d..f8175d4 100644 --- a/src/routes/$account/_layout/chats.lazy.tsx +++ b/src/routes/$account/_layout/chats.lazy.tsx @@ -1,505 +1,506 @@ -import { commands } from '@/commands' -import { ago, cn } from '@/commons' -import { Spinner } from '@/components/spinner' -import { User } from '@/components/user' +import { commands } from "@/commands"; +import { ago, cn } from "@/commons"; +import { Spinner } from "@/components/spinner"; +import { User } from "@/components/user"; import { - ArrowRight, - CaretDown, - CirclesFour, - Plus, - X, -} from '@phosphor-icons/react' -import * as Dialog from '@radix-ui/react-dialog' -import * as Progress from '@radix-ui/react-progress' -import * as ScrollArea from '@radix-ui/react-scroll-area' -import { useQuery } from '@tanstack/react-query' -import { Link, Outlet, createLazyFileRoute } from '@tanstack/react-router' -import { listen } from '@tauri-apps/api/event' -import { Menu, MenuItem, PredefinedMenuItem } from '@tauri-apps/api/menu' -import { readText, writeText } from '@tauri-apps/plugin-clipboard-manager' -import { message } from '@tauri-apps/plugin-dialog' -import { open } from '@tauri-apps/plugin-shell' -import { type NostrEvent, nip19 } from 'nostr-tools' -import { useCallback, useEffect, useRef, useState, useTransition } from 'react' -import { Virtualizer } from 'virtua' + ArrowRight, + CaretDown, + CirclesFour, + Plus, + X, +} from "@phosphor-icons/react"; +import * as Dialog from "@radix-ui/react-dialog"; +import * as Progress from "@radix-ui/react-progress"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { useQuery } from "@tanstack/react-query"; +import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router"; +import { listen } from "@tauri-apps/api/event"; +import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { message } from "@tauri-apps/plugin-dialog"; +import { open } from "@tauri-apps/plugin-shell"; +import { type NostrEvent, nip19 } from "nostr-tools"; +import { useCallback, useEffect, useRef, useState, useTransition } from "react"; +import { Virtualizer } from "virtua"; type EventPayload = { - event: string - sender: string -} + event: string; + sender: string; +}; -export const Route = createLazyFileRoute('/$account/_layout/chats')({ - component: Screen, -}) +export const Route = createLazyFileRoute("/$account/_layout/chats")({ + component: Screen, +}); function Screen() { - return ( -
-
-
- -
-
- -
-
- ) + return ( +
+
+
+ +
+
+ +
+
+ ); } function Header() { - const { platform } = Route.useRouteContext() - const { account } = Route.useParams() + const { platform } = Route.useRouteContext(); + const { account } = Route.useParams(); - return ( -
- -
- - - - -
-
- ) + return ( +
+ +
+ + + + +
+
+ ); } function ChatList() { - const { account } = Route.useParams() - const { queryClient } = Route.useRouteContext() - const { isLoading, data } = useQuery({ - queryKey: ['chats'], - queryFn: async () => { - const res = await commands.getChats() + const { account } = Route.useParams(); + const { queryClient } = Route.useRouteContext(); + const { isLoading, data } = useQuery({ + queryKey: ["chats"], + queryFn: async () => { + const res = await commands.getChats(); - if (res.status === 'ok') { - const raw = res.data - const events = raw.map((item) => JSON.parse(item) as NostrEvent) + if (res.status === "ok") { + const raw = res.data; + const events = raw.map((item) => JSON.parse(item) as NostrEvent); - return events - } else { - throw new Error(res.error) - } - }, - select: (data) => data.sort((a, b) => b.created_at - a.created_at), - refetchOnMount: false, - refetchOnWindowFocus: false, - }) + return events; + } else { + throw new Error(res.error); + } + }, + select: (data) => data.sort((a, b) => b.created_at - a.created_at), + refetchOnMount: false, + refetchOnWindowFocus: false, + }); - const [isSync, setIsSync] = useState(false) - const [progress, setProgress] = useState(0) + const [isSync, setIsSync] = useState(false); + const [progress, setProgress] = useState(0); - useEffect(() => { - const timer = setInterval( - () => setProgress((prev) => (prev <= 100 ? prev + 4 : 100)), - 1200, - ) - return () => clearInterval(timer) - }, []) + useEffect(() => { + const timer = setInterval( + () => setProgress((prev) => (prev <= 100 ? prev + 4 : 100)), + 1200, + ); + return () => clearInterval(timer); + }, []); - useEffect(() => { - const unlisten = listen('synchronized', async () => { - await queryClient.refetchQueries({ queryKey: ['chats'] }) - setIsSync(true) - }) + useEffect(() => { + const unlisten = listen("synchronized", async () => { + await queryClient.refetchQueries({ queryKey: ["chats"] }); + setIsSync(true); + }); - return () => { - unlisten.then((f) => f()) - } - }, []) + return () => { + unlisten.then((f) => f()); + }; + }, []); - useEffect(() => { - const unlisten = listen('event', async (data) => { - const event: NostrEvent = JSON.parse(data.payload.event) - const chats: NostrEvent[] = await queryClient.getQueryData(['chats']) + useEffect(() => { + const unlisten = listen("event", async (data) => { + const event: NostrEvent = JSON.parse(data.payload.event); + const chats: NostrEvent[] = await queryClient.getQueryData(["chats"]); - if (chats) { - const index = chats.findIndex((item) => item.pubkey === event.pubkey) + if (chats) { + const index = chats.findIndex((item) => item.pubkey === event.pubkey); - if (index === -1) { - await queryClient.setQueryData( - ['chats'], - (prevEvents: NostrEvent[]) => { - if (!prevEvents) return prevEvents - if (event.pubkey === account) return + if (index === -1) { + await queryClient.setQueryData( + ["chats"], + (prevEvents: NostrEvent[]) => { + if (!prevEvents) return prevEvents; + if (event.pubkey === account) return; - return [event, ...prevEvents] - }, - ) - } else { - const newEvents = [...chats] - newEvents[index] = { - ...event, - } + return [event, ...prevEvents]; + }, + ); + } else { + const newEvents = [...chats]; + newEvents[index] = { + ...event, + }; - await queryClient.setQueryData(['chats'], newEvents) - } - } - }) + await queryClient.setQueryData(["chats"], newEvents); + } + } + }); - return () => { - unlisten.then((f) => f()) - } - }, []) + return () => { + unlisten.then((f) => f()); + }; + }, []); - return ( - - - {isLoading ? ( - <> - {[...Array(5).keys()].map((i) => ( -
-
-
-
- ))} - - ) : isSync && !data.length ? ( -
-
- No chats. -
-
- ) : ( - data.map((item) => ( - - {({ isActive, isTransitioning }) => ( - - - -
-
- - - {account === item.pubkey ? '(you)' : ''} - -
- {isTransitioning ? ( - - ) : ( - - {ago(item.created_at)} - - )} -
-
-
- )} - - )) - )} - - {!isSync ? : null} - - - - - - ) + return ( + + + {isLoading ? ( + <> + {[...Array(5).keys()].map((i) => ( +
+
+
+
+ ))} + + ) : isSync && !data.length ? ( +
+
+ No chats. +
+
+ ) : ( + data.map((item) => ( + + {({ isActive, isTransitioning }) => ( + + + +
+
+ + + {account === item.pubkey ? "(you)" : ""} + +
+ {isTransitioning ? ( + + ) : ( + + {ago(item.created_at)} + + )} +
+
+
+ )} + + )) + )} + + {!isSync ? : null} + + + + + + ); } function SyncPopup({ progress }: { progress: number }) { - return ( -
-
- - - - Syncing message... -
-
- ) + return ( +
+
+
+ + + + Syncing message... +
+
+ ); } function Compose() { - const [isOpen, setIsOpen] = useState(false) - const [target, setTarget] = useState('') - const [newMessage, setNewMessage] = useState('') - const [isPending, startTransition] = useTransition() + const [isOpen, setIsOpen] = useState(false); + const [target, setTarget] = useState(""); + const [newMessage, setNewMessage] = useState(""); + const [isPending, startTransition] = useTransition(); - const { account } = Route.useParams() - const { isLoading, data: contacts } = useQuery({ - queryKey: ['contacts', account], - queryFn: async () => { - const res = await commands.getContactList() + const { account } = Route.useParams(); + const { isLoading, data: contacts } = useQuery({ + queryKey: ["contacts", account], + queryFn: async () => { + const res = await commands.getContactList(); - if (res.status === 'ok') { - return res.data - } else { - return [] - } - }, - refetchOnWindowFocus: false, - enabled: isOpen, - }) + if (res.status === "ok") { + return res.data; + } else { + return []; + } + }, + refetchOnWindowFocus: false, + enabled: isOpen, + }); - const navigate = Route.useNavigate() - const scrollRef = useRef(null) + const navigate = Route.useNavigate(); + const scrollRef = useRef(null); - const pasteFromClipboard = async () => { - const val = await readText() - setTarget(val) - } + const pasteFromClipboard = async () => { + const val = await readText(); + setTarget(val); + }; - const sendMessage = () => { - startTransition(async () => { - if (!newMessage.length) return - if (!target.length) return - if (!target.startsWith('npub1')) { - await message('You must enter the public key as npub', { - title: 'Send Message', - kind: 'error', - }) - return - } + const sendMessage = () => { + startTransition(async () => { + if (!newMessage.length) return; + if (!target.length) return; + if (!target.startsWith("npub1")) { + await message("You must enter the public key as npub", { + title: "Send Message", + kind: "error", + }); + return; + } - const decoded = nip19.decode(target) - let id: string + const decoded = nip19.decode(target); + let id: string; - if (decoded.type !== 'npub') { - await message('You must enter the public key as npub', { - title: 'Send Message', - kind: 'error', - }) - return - } else { - id = decoded.data - } + if (decoded.type !== "npub") { + await message("You must enter the public key as npub", { + title: "Send Message", + kind: "error", + }); + return; + } else { + id = decoded.data; + } - // Connect to user's inbox relays - const connect = await commands.connectInboxRelays(target, false) + // Connect to user's inbox relays + const connect = await commands.connectInboxRelays(target, false); - // Send message - if (connect.status === 'ok') { - const res = await commands.sendMessage(id, newMessage) + // Send message + if (connect.status === "ok") { + const res = await commands.sendMessage(id, newMessage); - if (res.status === 'ok') { - setTarget('') - setNewMessage('') - setIsOpen(false) + if (res.status === "ok") { + setTarget(""); + setNewMessage(""); + setIsOpen(false); - navigate({ - to: '/$account/chats/$id', - params: { account, id }, - }) - } else { - await message(res.error, { title: 'Send Message', kind: 'error' }) - return - } - } else { - await message(connect.error, { - title: 'Connect Inbox Relays', - kind: 'error', - }) - return - } - }) - } + navigate({ + to: "/$account/chats/$id", + params: { account, id }, + }); + } else { + await message(res.error, { title: "Send Message", kind: "error" }); + return; + } + } else { + await message(connect.error, { + title: "Connect Inbox Relays", + kind: "error", + }); + return; + } + }); + }; - return ( - - - - - - - -
-
- Send to - - - -
-
- To: -
- setTarget(e.target.value)} - disabled={isPending} - className="w-full pr-14 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" - /> - -
-
-
- Message: - setNewMessage(e.target.value)} - disabled={isPending} - className="flex-1 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" - /> - -
-
- - - - {isLoading ? ( -
- -
- ) : !contacts?.length ? ( -
-

Contact is empty.

-
- ) : ( - contacts?.map((contact) => ( - - )) - )} -
-
- - - - -
-
-
-
- ) + return ( + + + + + + + +
+
+ Send to + + + +
+
+ To: +
+ setTarget(e.target.value)} + disabled={isPending} + className="w-full pr-14 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" + /> + +
+
+
+ Message: + setNewMessage(e.target.value)} + disabled={isPending} + className="flex-1 h-9 bg-transparent focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600" + /> + +
+
+ + + + {isLoading ? ( +
+ +
+ ) : !contacts?.length ? ( +
+

Contact is empty.

+
+ ) : ( + contacts?.map((contact) => ( + + )) + )} +
+
+ + + + +
+
+
+
+ ); } function CurrentUser() { - const params = Route.useParams() - const navigate = Route.useNavigate() + const params = Route.useParams(); + const navigate = Route.useNavigate(); - const showContextMenu = useCallback(async (e: React.MouseEvent) => { - e.preventDefault() + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); - const menuItems = await Promise.all([ - MenuItem.new({ - text: 'Copy Public Key', - action: async () => { - const npub = nip19.npubEncode(params.account) - await writeText(npub) - }, - }), - MenuItem.new({ - text: 'Settings', - action: () => navigate({ to: '/' }), - }), - MenuItem.new({ - text: 'Feedback', - action: async () => await open('https://github.com/lumehq/coop/issues'), - }), - PredefinedMenuItem.new({ item: 'Separator' }), - MenuItem.new({ - text: 'Switch account', - action: () => navigate({ to: '/' }), - }), - ]) + const menuItems = await Promise.all([ + MenuItem.new({ + text: "Copy Public Key", + action: async () => { + const npub = nip19.npubEncode(params.account); + await writeText(npub); + }, + }), + MenuItem.new({ + text: "Settings", + action: () => navigate({ to: "/" }), + }), + MenuItem.new({ + text: "Feedback", + action: async () => await open("https://github.com/lumehq/coop/issues"), + }), + PredefinedMenuItem.new({ item: "Separator" }), + MenuItem.new({ + text: "Switch account", + action: () => navigate({ to: "/" }), + }), + ]); - const menu = await Menu.new({ - items: menuItems, - }) + const menu = await Menu.new({ + items: menuItems, + }); - await menu.popup().catch((e) => console.error(e)) - }, []) + await menu.popup().catch((e) => console.error(e)); + }, []); - return ( - - ) + return ( + + ); }