feat: pull relay sets button
This commit is contained in:
@@ -6,12 +6,12 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function AddNewRelaySet() {
|
||||
const { t } = useTranslation()
|
||||
const { addRelaySet } = useFavoriteRelays()
|
||||
const { createRelaySet } = useFavoriteRelays()
|
||||
const [newRelaySetName, setNewRelaySetName] = useState('')
|
||||
|
||||
const saveRelaySet = () => {
|
||||
if (!newRelaySetName) return
|
||||
addRelaySet(newRelaySetName)
|
||||
createRelaySet(newRelaySetName)
|
||||
setNewRelaySetName('')
|
||||
}
|
||||
|
||||
|
||||
186
src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx
Normal file
186
src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
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 { getReplaceableEventIdentifier } from '@/lib/event'
|
||||
import { tagNameEquals } from '@/lib/tag'
|
||||
import { isWebsocketUrl, simplifyUrl } from '@/lib/url'
|
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { TRelaySet } from '@/types'
|
||||
import { CloudDownload } from 'lucide-react'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import RelaySetCard from '../RelaySetCard'
|
||||
|
||||
export default function PullRelaySetsButton() {
|
||||
const { t } = useTranslation()
|
||||
const { pubkey } = useNostr()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
variant="link"
|
||||
className="text-muted-foreground hover:no-underline hover:text-foreground p-0 h-fit"
|
||||
disabled={!pubkey}
|
||||
>
|
||||
<CloudDownload />
|
||||
{t('Pull relay sets')}
|
||||
</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 { addRelaySets, relaySets: existingRelaySets } = useFavoriteRelays()
|
||||
const [initialed, setInitialed] = useState(false)
|
||||
const [relaySetEventMap, setRelaySetEventMap] = useState<Map<string, Event>>(new Map())
|
||||
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
|
||||
}
|
||||
)
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
const relaySetIds = new Set<string>(existingRelaySets.map((r) => r.id))
|
||||
const relaySets: TRelaySet[] = []
|
||||
const relaySetEventMap = new Map<string, Event>()
|
||||
events.forEach((evt) => {
|
||||
const id = getReplaceableEventIdentifier(evt)
|
||||
if (!id || relaySetIds.has(id)) return
|
||||
|
||||
relaySetIds.add(id)
|
||||
const relayUrls = evt.tags
|
||||
.filter(tagNameEquals('relay'))
|
||||
.map((tag) => tag[1])
|
||||
.filter((url) => url && isWebsocketUrl(url))
|
||||
if (!relayUrls.length) return
|
||||
|
||||
let title = evt.tags.find(tagNameEquals('title'))?.[1]
|
||||
if (!title) {
|
||||
title = relayUrls.length === 1 ? simplifyUrl(relayUrls[0]) : id
|
||||
}
|
||||
relaySets.push({ id, name: title, relayUrls })
|
||||
relaySetEventMap.set(id, evt)
|
||||
})
|
||||
|
||||
setRelaySets(relaySets)
|
||||
setRelaySetEventMap(relaySetEventMap)
|
||||
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={() => {
|
||||
const selectedRelaySets = selectedRelaySetIds
|
||||
.map((id) => relaySetEventMap.get(id))
|
||||
.filter(Boolean) as Event[]
|
||||
if (selectedRelaySets.length > 0) {
|
||||
addRelaySets(selectedRelaySets)
|
||||
close?.()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedRelaySetIds.length > 0
|
||||
? t('Pull n relay sets', { n: selectedRelaySetIds.length })
|
||||
: t('Pull')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { RelaySetsSettingComponentProvider } from './provider'
|
||||
import RelayItem from './RelayItem'
|
||||
import RelaySet from './RelaySet'
|
||||
import TemporaryRelaySet from './TemporaryRelaySet'
|
||||
import PullRelaySetsButton from './PullRelaySetsButton'
|
||||
|
||||
export default function FavoriteRelaysSetting() {
|
||||
const { t } = useTranslation()
|
||||
@@ -16,7 +17,12 @@ export default function FavoriteRelaysSetting() {
|
||||
<div className="space-y-4">
|
||||
<TemporaryRelaySet />
|
||||
<div className="space-y-2">
|
||||
<div className="text-muted-foreground font-semibold select-none">{t('Relay sets')}</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="text-muted-foreground font-semibold select-nones shrink-0">
|
||||
{t('Relay sets')}
|
||||
</div>
|
||||
<PullRelaySetsButton />
|
||||
</div>
|
||||
{relaySets.map((relaySet) => (
|
||||
<RelaySet key={relaySet.id} relaySet={relaySet} />
|
||||
))}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function RelaySetCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full border rounded-lg p-4 ${select ? 'border-highlight bg-highlight/5' : 'clickable'}`}
|
||||
className={`w-full border rounded-lg p-4 clickable ${select ? 'border-highlight bg-highlight/5' : ''}`}
|
||||
onClick={() => onSelectChange(!select)}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -175,12 +175,12 @@ function RelaySetItem({ set, urls }: { set: TRelaySet; urls: string[] }) {
|
||||
function SaveToNewSet({ urls }: { urls: string[] }) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { addRelaySet } = useFavoriteRelays()
|
||||
const { createRelaySet } = useFavoriteRelays()
|
||||
|
||||
const handleSave = () => {
|
||||
const newSetName = prompt(t('Enter a name for the new relay set'))
|
||||
if (newSetName) {
|
||||
addRelaySet(newSetName, urls)
|
||||
createRelaySet(newSetName, urls)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,11 @@ export default {
|
||||
'R & W': 'قراءة وكتابة',
|
||||
Read: 'قراءة',
|
||||
Write: 'كتابة',
|
||||
'Pull relay sets': 'سحب مجموعات الريلاي',
|
||||
'Select the relay sets you want to pull': 'اختر مجموعات الريلاي التي تريد استلامها',
|
||||
'No relay sets found': 'لم يتم العثور على مجموعات ريلاي',
|
||||
'Pull n relay sets': 'سحب {{n}} مجموعات ريلاي',
|
||||
Pull: 'سحب',
|
||||
'Select all': 'اختر الكل',
|
||||
'Relay Sets': 'مجموعات الريلاي',
|
||||
'Read & Write Relays': 'ريلايات القراءة والكتابة',
|
||||
|
||||
@@ -116,6 +116,11 @@ export default {
|
||||
'R & W': 'R & W',
|
||||
Read: 'Lesen',
|
||||
Write: 'Schreiben',
|
||||
'Pull relay sets': 'Relay-Sets abrufen',
|
||||
'Select the relay sets you want to pull': 'Wähle die Relay-Sets, die du abrufen möchtest',
|
||||
'No relay sets found': 'Keine Relay-Sets gefunden',
|
||||
'Pull n relay sets': 'Hole {{n}} Relay-Sets',
|
||||
Pull: 'Abrufen',
|
||||
'Select all': 'Alle auswählen',
|
||||
'Relay Sets': 'Relay-Sets',
|
||||
'Read & Write Relays': 'Lese- & Schreib-Relays',
|
||||
|
||||
@@ -115,6 +115,11 @@ export default {
|
||||
'R & W': 'R & W',
|
||||
Read: 'Read',
|
||||
Write: 'Write',
|
||||
'Pull relay sets': 'Pull relay sets',
|
||||
'Select the relay sets you want to pull': 'Select the relay sets you want to pull',
|
||||
'No relay sets found': 'No relay sets found',
|
||||
'Pull n relay sets': 'Pull {{n}} relay sets',
|
||||
Pull: 'Pull',
|
||||
'Select all': 'Select all',
|
||||
'Relay Sets': 'Relay Sets',
|
||||
'Read & Write Relays': 'Read & Write Relays',
|
||||
|
||||
@@ -116,6 +116,12 @@ export default {
|
||||
'R & W': 'L y E',
|
||||
Read: 'Leer',
|
||||
Write: 'Escribir',
|
||||
'Pull relay sets': 'Recibir conjuntos de relés',
|
||||
'Select the relay sets you want to pull':
|
||||
'Selecciona los conjuntos de relés que deseas recibir',
|
||||
'No relay sets found': 'No se encontraron conjuntos de relés',
|
||||
'Pull n relay sets': 'Recibir {{n}} conjuntos de relés',
|
||||
Pull: 'Recibir',
|
||||
'Select all': 'Seleccionar todo',
|
||||
'Relay Sets': 'Conjuntos de relés',
|
||||
'Read & Write Relays': 'Relés de lectura y escritura',
|
||||
|
||||
@@ -116,6 +116,11 @@ export default {
|
||||
'R & W': 'R & W',
|
||||
Read: 'Lire',
|
||||
Write: 'Écrire',
|
||||
'Pull relay sets': 'Récupérer les groupes de relais',
|
||||
'Select the relay sets you want to pull': 'Sélectionnez les groupes de relais à récupérer',
|
||||
'No relay sets found': 'Aucun groupe de relais trouvé',
|
||||
'Pull n relay sets': 'Récupérer {{n}} groupes de relais',
|
||||
Pull: 'Récupérer',
|
||||
'Select all': 'Tout sélectionner',
|
||||
'Relay Sets': 'Groupes de relais',
|
||||
'Read & Write Relays': 'Relais lecture & écriture',
|
||||
|
||||
@@ -116,6 +116,11 @@ export default {
|
||||
'R & W': 'L & S',
|
||||
Read: 'Leggi',
|
||||
Write: 'Scrivi',
|
||||
'Pull relay sets': 'Ottieni set di relay',
|
||||
'Select the relay sets you want to pull': 'Selezionare i set di relay che si desidera ottenere',
|
||||
'No relay sets found': 'Nessun set di relay trovato',
|
||||
'Pull n relay sets': 'Ottieni {{n}} set di relay',
|
||||
Pull: 'Ottieni',
|
||||
'Select all': 'Seleziona tutto',
|
||||
'Relay Sets': 'Set di Relay',
|
||||
'Read & Write Relays': 'Relay Leggi & Scrivi',
|
||||
|
||||
@@ -116,6 +116,11 @@ export default {
|
||||
'R & W': '読&書',
|
||||
Read: '読む',
|
||||
Write: '書く',
|
||||
'Pull relay sets': 'リレイセットをプル',
|
||||
'Select the relay sets you want to pull': 'プルするリレイセットを選択',
|
||||
'No relay sets found': 'リレイセットが見つかりません',
|
||||
'Pull n relay sets': '{{n}} 個のリレイセットをプル',
|
||||
Pull: 'プル',
|
||||
'Select all': 'すべて選択',
|
||||
'Relay Sets': 'リレイセット',
|
||||
'Read & Write Relays': '読み&書きリレイ',
|
||||
|
||||
@@ -115,6 +115,11 @@ export default {
|
||||
'R & W': 'O & Z',
|
||||
Read: 'Odczyt',
|
||||
Write: 'Zapis',
|
||||
'Pull relay sets': 'Pobierz zestaw transmiterów',
|
||||
'Select the relay sets you want to pull': 'Wybierz zestaw transmiterów do pobrania',
|
||||
'No relay sets found': 'Nie znaleziono zestawu transmiterów',
|
||||
'Pull n relay sets': 'Pobierz {{n}} zestawów transmiterów',
|
||||
Pull: 'Pobierz',
|
||||
'Select all': 'Wszystkie',
|
||||
'Relay Sets': 'Grupy transmiterów',
|
||||
'Read & Write Relays': 'Transmitery zapisu i odczytu',
|
||||
|
||||
@@ -115,6 +115,11 @@ export default {
|
||||
'R & W': 'Leitura & Escrita',
|
||||
Read: 'Ler',
|
||||
Write: 'Escrever',
|
||||
'Pull relay sets': 'Receber conjuntos de relé',
|
||||
'Select the relay sets you want to pull': 'Selecione os conjuntos de relé que deseja receber',
|
||||
'No relay sets found': 'Nenhum conjunto de relé encontrado',
|
||||
'Pull n relay sets': 'Receber {{n}} conjuntos de relé',
|
||||
Pull: 'Receber',
|
||||
'Select all': 'Selecionar todos',
|
||||
'Relay Sets': 'Conjuntos de relé',
|
||||
'Read & Write Relays': 'Relés de Leitura & Escrita',
|
||||
|
||||
@@ -116,6 +116,11 @@ export default {
|
||||
'R & W': 'Leitura & Escrita',
|
||||
Read: 'Ler',
|
||||
Write: 'Escrever',
|
||||
'Pull relay sets': 'Receber conjuntos de relé',
|
||||
'Select the relay sets you want to pull': 'Selecione os conjuntos de relé que deseja receber',
|
||||
'No relay sets found': 'Nenhum conjunto de relé encontrado',
|
||||
'Pull n relay sets': 'Receber {{n}} conjuntos de relé',
|
||||
Pull: 'Receber',
|
||||
'Select all': 'Selecionar todos',
|
||||
'Relay Sets': 'Conjuntos de relé',
|
||||
'Read & Write Relays': 'Relés de Leitura & Escrita',
|
||||
|
||||
@@ -117,6 +117,11 @@ export default {
|
||||
'R & W': 'Чтение & Запись',
|
||||
Read: 'Читать',
|
||||
Write: 'Писать',
|
||||
'Pull relay sets': 'Получить наборы ретрансляторов',
|
||||
'Select the relay sets you want to pull': 'Выберите наборы ретрансляторов для получения',
|
||||
'No relay sets found': 'Наборы ретрансляторов не найдены',
|
||||
'Pull n relay sets': 'Получить {{n}} наборов ретрансляторов',
|
||||
Pull: 'Получить',
|
||||
'Select all': 'Выбрать все',
|
||||
'Relay Sets': 'Наборы ретрансляторов',
|
||||
'Read & Write Relays': 'Ретрансляторы для чтения и записи',
|
||||
|
||||
@@ -115,6 +115,11 @@ export default {
|
||||
'R & W': '读写',
|
||||
Read: '只读',
|
||||
Write: '只写',
|
||||
'Pull relay sets': '拉取服务器组',
|
||||
'Select the relay sets you want to pull': '选择要拉取的服务器组',
|
||||
'No relay sets found': '未找到服务器组',
|
||||
'Pull n relay sets': '拉取 {{n}} 个服务器组',
|
||||
Pull: '拉取',
|
||||
'Select all': '全选',
|
||||
'Relay Sets': '服务器组',
|
||||
Mailbox: '邮箱',
|
||||
|
||||
@@ -16,7 +16,8 @@ type TFavoriteRelaysContext = {
|
||||
addFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
||||
deleteFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
||||
relaySets: TRelaySet[]
|
||||
addRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
|
||||
createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
|
||||
addRelaySets: (newRelaySetEvents: Event[]) => Promise<void>
|
||||
deleteRelaySet: (id: string) => Promise<void>
|
||||
updateRelaySet: (newSet: TRelaySet) => Promise<void>
|
||||
}
|
||||
@@ -149,7 +150,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
||||
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
|
||||
}
|
||||
|
||||
const addRelaySet = async (relaySetName: string, relayUrls: string[] = []) => {
|
||||
const createRelaySet = async (relaySetName: string, relayUrls: string[] = []) => {
|
||||
const normalizedUrls = relayUrls
|
||||
.map((url) => normalizeUrl(url))
|
||||
.filter((url) => isWebsocketUrl(url))
|
||||
@@ -170,6 +171,15 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
||||
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
|
||||
}
|
||||
|
||||
const addRelaySets = async (newRelaySetEvents: Event[]) => {
|
||||
const favoriteRelaysDraftEvent = createFavoriteRelaysDraftEvent(favoriteRelays, [
|
||||
...relaySetEvents,
|
||||
...newRelaySetEvents
|
||||
])
|
||||
const newFavoriteRelaysEvent = await publish(favoriteRelaysDraftEvent)
|
||||
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
|
||||
}
|
||||
|
||||
const deleteRelaySet = async (id: string) => {
|
||||
const newRelaySetEvents = relaySetEvents.filter((event) => {
|
||||
return getReplaceableEventIdentifier(event) !== id
|
||||
@@ -203,7 +213,8 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
||||
addFavoriteRelays,
|
||||
deleteFavoriteRelays,
|
||||
relaySets,
|
||||
addRelaySet,
|
||||
createRelaySet,
|
||||
addRelaySets,
|
||||
deleteRelaySet,
|
||||
updateRelaySet
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user