From 864d8c617fa5f8241bb894785828fe6d6d9d44ca Mon Sep 17 00:00:00 2001 From: codytseng Date: Sun, 6 Apr 2025 16:21:44 +0800 Subject: [PATCH] feat: pull relay sets button --- .../FavoriteRelaysSetting/AddNewRelaySet.tsx | 4 +- .../PullRelaySetsButton.tsx | 186 ++++++++++++++++++ .../FavoriteRelaysSetting/index.tsx | 8 +- src/components/RelaySetCard/index.tsx | 2 +- .../SaveRelayDropdownMenu/index.tsx | 4 +- src/i18n/locales/ar.ts | 5 + src/i18n/locales/de.ts | 5 + src/i18n/locales/en.ts | 5 + src/i18n/locales/es.ts | 6 + src/i18n/locales/fr.ts | 5 + src/i18n/locales/it.ts | 5 + src/i18n/locales/ja.ts | 5 + src/i18n/locales/pl.ts | 5 + src/i18n/locales/pt-BR.ts | 5 + src/i18n/locales/pt-PT.ts | 5 + src/i18n/locales/ru.ts | 5 + src/i18n/locales/zh.ts | 5 + src/providers/FavoriteRelaysProvider.tsx | 17 +- 18 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx diff --git a/src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx b/src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx index e09059de..0c8de053 100644 --- a/src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx +++ b/src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx @@ -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('') } diff --git a/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx b/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx new file mode 100644 index 00000000..b07082a3 --- /dev/null +++ b/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx @@ -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 = ( + + ) + + if (isSmallScreen) { + return ( + + {trigger} + +
+ + {t('Select the relay sets you want to pull')} + + + setOpen(false)} /> +
+
+
+ ) + } + + return ( + + {trigger} + + + {t('Select the relay sets you want to pull')} + + + setOpen(false)} /> + + + ) +} + +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>(new Map()) + const [relaySets, setRelaySets] = useState([]) + const [selectedRelaySetIds, setSelectedRelaySetIds] = useState([]) + + 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(existingRelaySets.map((r) => r.id)) + const relaySets: TRelaySet[] = [] + const relaySetEventMap = new Map() + 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
{t('loading...')}
+ if (!relaySets.length) { + return
{t('No relay sets found')}
+ } + + return ( +
+
+ {relaySets.map((relaySet) => ( + { + if (select) { + setSelectedRelaySetIds([...selectedRelaySetIds, relaySet.id]) + } else { + setSelectedRelaySetIds(selectedRelaySetIds.filter((id) => id !== relaySet.id)) + } + }} + /> + ))} +
+
+ + +
+
+ ) +} diff --git a/src/components/FavoriteRelaysSetting/index.tsx b/src/components/FavoriteRelaysSetting/index.tsx index 268277c7..8618ef62 100644 --- a/src/components/FavoriteRelaysSetting/index.tsx +++ b/src/components/FavoriteRelaysSetting/index.tsx @@ -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() {
-
{t('Relay sets')}
+
+
+ {t('Relay sets')} +
+ +
{relaySets.map((relaySet) => ( ))} diff --git a/src/components/RelaySetCard/index.tsx b/src/components/RelaySetCard/index.tsx index 58c15410..60d66617 100644 --- a/src/components/RelaySetCard/index.tsx +++ b/src/components/RelaySetCard/index.tsx @@ -18,7 +18,7 @@ export default function RelaySetCard({ return (
onSelectChange(!select)} >
diff --git a/src/components/SaveRelayDropdownMenu/index.tsx b/src/components/SaveRelayDropdownMenu/index.tsx index 2bf891dd..a22153b9 100644 --- a/src/components/SaveRelayDropdownMenu/index.tsx +++ b/src/components/SaveRelayDropdownMenu/index.tsx @@ -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) } } diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index d06db8ee..531fbbd0 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -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': 'ريلايات القراءة والكتابة', diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index bb2e0827..ec53df3b 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -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', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index f3b92e8e..b2683a56 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index ecd3fafe..815821d4 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -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', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index da4e65c3..32ad7770 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -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', diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index d92299ff..bd59a3b4 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -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', diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index f439b7e4..30f1bb80 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -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': '読み&書きリレイ', diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 2f1244a7..3651d156 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -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', diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 114be3b8..aa5936d0 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -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', diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index f03bed69..e3f8ad9b 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -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', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 6c309ce5..bbd2d197 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -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': 'Ретрансляторы для чтения и записи', diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index b603f279..0e7afcfa 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -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: '邮箱', diff --git a/src/providers/FavoriteRelaysProvider.tsx b/src/providers/FavoriteRelaysProvider.tsx index 2b56db30..0f3c4dc2 100644 --- a/src/providers/FavoriteRelaysProvider.tsx +++ b/src/providers/FavoriteRelaysProvider.tsx @@ -16,7 +16,8 @@ type TFavoriteRelaysContext = { addFavoriteRelays: (relayUrls: string[]) => Promise deleteFavoriteRelays: (relayUrls: string[]) => Promise relaySets: TRelaySet[] - addRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise + createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise + addRelaySets: (newRelaySetEvents: Event[]) => Promise deleteRelaySet: (id: string) => Promise updateRelaySet: (newSet: TRelaySet) => Promise } @@ -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 }}