Files
smesh/src/components/RelaySetsSetting/PullFromRelaysButton.tsx
2025-01-13 16:53:07 +08:00

172 lines
5.3 KiB
TypeScript

import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from '@/components/ui/drawer'
import { BIG_RELAY_URLS } from '@/constants'
import { tagNameEquals } from '@/lib/tag'
import { isWebsocketUrl, simplifyUrl } from '@/lib/url'
import { useNostr } from '@/providers/NostrProvider'
import { useRelaySets } from '@/providers/RelaySetsProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import client from '@/services/client.service'
import { TRelaySet } from '@/types'
import { CloudDownload } from 'lucide-react'
import { kinds } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import RelaySetCard from '../RelaySetCard'
export default function PullFromRelaysButton() {
const { t } = useTranslation()
const { pubkey } = useNostr()
const { isSmallScreen } = useScreenSize()
const [open, setOpen] = useState(false)
const trigger = (
<Button variant="secondary" className="w-full" disabled={!pubkey}>
<CloudDownload />
{t('Pull from relays')}
</Button>
)
if (isSmallScreen) {
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[90vh]">
<div className="flex flex-col p-4 gap-4 overflow-auto">
<DrawerHeader>
<DrawerTitle>{t('Select the relay sets you want to pull')}</DrawerTitle>
<DrawerDescription className="hidden" />
</DrawerHeader>
<RemoteRelaySets close={() => setOpen(false)} />
</div>
</DrawerContent>
</Drawer>
)
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="max-h-[90vh] overflow-auto">
<DialogHeader>
<DialogTitle>{t('Select the relay sets you want to pull')}</DialogTitle>
<DialogDescription className="hidden" />
</DialogHeader>
<RemoteRelaySets close={() => setOpen(false)} />
</DialogContent>
</Dialog>
)
}
function RemoteRelaySets({ close }: { close?: () => void }) {
const { t } = useTranslation()
const { pubkey, relayList } = useNostr()
const { mergeRelaySets } = useRelaySets()
const [initialed, setInitialed] = useState(false)
const [relaySets, setRelaySets] = useState<TRelaySet[]>([])
const [selectedRelaySetIds, setSelectedRelaySetIds] = useState<string[]>([])
useEffect(() => {
if (!pubkey) return
const init = async () => {
setInitialed(false)
const events = await client.fetchEvents(
(relayList?.write ?? []).concat(BIG_RELAY_URLS).slice(0, 4),
{
kinds: [kinds.Relaysets],
authors: [pubkey],
limit: 50
}
)
setRelaySets(
events
.map((evt) => {
const id = evt.tags.find(tagNameEquals('d'))?.[1]
if (!id) return null
const relayUrls = evt.tags
.filter(tagNameEquals('relay'))
.map((tag) => tag[1])
.filter((url) => url && isWebsocketUrl(url))
if (!relayUrls.length) return null
let title = evt.tags.find(tagNameEquals('title'))?.[1]
if (!title) {
title = relayUrls.length === 1 ? simplifyUrl(relayUrls[0]) : id
}
return { id, name: title, relayUrls }
})
.filter(Boolean) as TRelaySet[]
)
setInitialed(true)
}
init()
}, [pubkey])
if (!pubkey) return null
if (!initialed) return <div className="text-center text-muted-foreground">{t('loading...')}</div>
if (!relaySets.length) {
return <div className="text-center text-muted-foreground">{t('No relay sets found')}</div>
}
return (
<div className="space-y-4">
<div className="space-y-2">
{relaySets.map((relaySet) => (
<RelaySetCard
key={relaySet.id}
relaySet={relaySet}
select={selectedRelaySetIds.includes(relaySet.id)}
onSelectChange={(select) => {
if (select) {
setSelectedRelaySetIds([...selectedRelaySetIds, relaySet.id])
} else {
setSelectedRelaySetIds(selectedRelaySetIds.filter((id) => id !== relaySet.id))
}
}}
/>
))}
</div>
<div className="flex gap-2">
<Button
className="w-20 shrink-0"
variant="secondary"
onClick={() => setSelectedRelaySetIds(relaySets.map((r) => r.id))}
>
{t('Select all')}
</Button>
<Button
className="w-full"
disabled={!selectedRelaySetIds.length}
onClick={() => {
if (selectedRelaySetIds.length > 0) {
mergeRelaySets(relaySets.filter((set) => selectedRelaySetIds.includes(set.id)))
close?.()
}
}}
>
{selectedRelaySetIds.length > 0
? t('Pull n relay sets', { n: selectedRelaySetIds.length })
: t('Pull')}
</Button>
</div>
</div>
)
}