feat: implement relay management
All checks were successful
Release to Staging / build_and_release (push) Successful in 46s
All checks were successful
Release to Staging / build_and_release (push) Successful in 46s
This commit is contained in:
parent
26accf4eca
commit
37457128c5
@ -141,6 +141,9 @@ export class MetadataController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getNDKRelayList = async (hexKey: string) =>
|
||||||
|
getRelayListForUser(hexKey, this.ndk)
|
||||||
|
|
||||||
public getMuteLists = async (
|
public getMuteLists = async (
|
||||||
pubkey?: string
|
pubkey?: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
@ -14,6 +14,13 @@ import { PreferencesSetting } from './preference'
|
|||||||
import { AdminSetting } from './admin'
|
import { AdminSetting } from './admin'
|
||||||
import { ProfileSection } from 'components/ProfileSection'
|
import { ProfileSection } from 'components/ProfileSection'
|
||||||
|
|
||||||
|
import 'styles/feed.css'
|
||||||
|
import 'styles/innerPage.css'
|
||||||
|
import 'styles/popup.css'
|
||||||
|
import 'styles/settings.css'
|
||||||
|
import 'styles/styles.css'
|
||||||
|
import 'styles/write.css'
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
export const SettingsPage = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
@ -67,7 +74,11 @@ const SettingTabs = () => {
|
|||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ path: appRoutes.settingsProfile, label: 'Profile', icon: <ProfileSVG /> },
|
{ path: appRoutes.settingsProfile, label: 'Profile', icon: <ProfileSVG /> },
|
||||||
{ path: appRoutes.settingsRelays, label: 'Relays (WIP)', icon: <RelaySVG /> },
|
{
|
||||||
|
path: appRoutes.settingsRelays,
|
||||||
|
label: 'Relays',
|
||||||
|
icon: <RelaySVG />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: appRoutes.settingsPreferences,
|
path: appRoutes.settingsPreferences,
|
||||||
label: 'Preferences (WIP)',
|
label: 'Preferences (WIP)',
|
||||||
|
@ -1,15 +1,286 @@
|
|||||||
|
import { NDKRelayList } from '@nostr-dev-kit/ndk'
|
||||||
import { InputField } from 'components/Inputs'
|
import { InputField } from 'components/Inputs'
|
||||||
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
|
import {
|
||||||
|
MetadataController,
|
||||||
|
RelayController,
|
||||||
|
UserRelaysType
|
||||||
|
} from 'controllers'
|
||||||
|
import { useAppSelector, useDidMount } from 'hooks'
|
||||||
|
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { log, LogType, normalizeWebSocketURL, now } from 'utils'
|
||||||
|
|
||||||
|
const READ_MARKER = 'read'
|
||||||
|
const WRITE_MARKER = 'write'
|
||||||
|
|
||||||
export const RelaySettings = () => {
|
export const RelaySettings = () => {
|
||||||
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const [ndkRelayList, setNDKRelayList] = useState<NDKRelayList | null>(null)
|
||||||
|
const [isPublishing, setIsPublishing] = useState(false)
|
||||||
|
|
||||||
|
const [inputValue, setInputValue] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchRelayList = async (pubkey: string) => {
|
||||||
|
const metadataController = await MetadataController.getInstance()
|
||||||
|
metadataController
|
||||||
|
.getNDKRelayList(pubkey)
|
||||||
|
.then((res) => {
|
||||||
|
setNDKRelayList(res)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(
|
||||||
|
`An error occurred in fetching user relay list: ${
|
||||||
|
err.message || err
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
setNDKRelayList(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userState.auth && userState.user?.pubkey) {
|
||||||
|
fetchRelayList(userState.user.pubkey as string)
|
||||||
|
} else {
|
||||||
|
setNDKRelayList(null)
|
||||||
|
}
|
||||||
|
}, [userState])
|
||||||
|
|
||||||
|
const handleAdd = async (relayUrl: string) => {
|
||||||
|
if (!ndkRelayList) return
|
||||||
|
|
||||||
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
|
|
||||||
|
const rawEvent = ndkRelayList.rawEvent()
|
||||||
|
|
||||||
|
const unsignedEvent: UnsignedEvent = {
|
||||||
|
pubkey: rawEvent.pubkey,
|
||||||
|
kind: kinds.RelayList,
|
||||||
|
tags: [...rawEvent.tags, ['r', normalizedUrl]],
|
||||||
|
content: rawEvent.content,
|
||||||
|
created_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(true)
|
||||||
|
|
||||||
|
const signedEvent = await window.nostr
|
||||||
|
?.signEvent(unsignedEvent)
|
||||||
|
.then((event) => event as Event)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to sign the event!')
|
||||||
|
log(true, LogType.Error, 'Failed to sign the event!', err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!signedEvent) {
|
||||||
|
setIsPublishing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishedOnRelays =
|
||||||
|
await RelayController.getInstance().publishOnRelays(
|
||||||
|
signedEvent,
|
||||||
|
ndkRelayList.writeRelayUrls
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle cases where publishing failed or succeeded
|
||||||
|
if (publishedOnRelays.length === 0) {
|
||||||
|
toast.error('Failed to publish relay list event on any relay')
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
|
||||||
|
'\n'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const newNDKRelayList = new NDKRelayList(ndkRelayList.ndk, signedEvent)
|
||||||
|
setNDKRelayList(newNDKRelayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemove = async (relayUrl: string) => {
|
||||||
|
if (!ndkRelayList) return
|
||||||
|
|
||||||
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
|
|
||||||
|
const rawEvent = ndkRelayList.rawEvent()
|
||||||
|
|
||||||
|
const nonRelayTags = rawEvent.tags.filter(
|
||||||
|
(tag) => tag[0] !== 'r' && tag[0] !== 'relay'
|
||||||
|
)
|
||||||
|
|
||||||
|
const relayTags = rawEvent.tags
|
||||||
|
.filter((tag) => tag[0] === 'r' || tag[0] === 'relay')
|
||||||
|
.filter((tag) => normalizeWebSocketURL(tag[1]) !== normalizedUrl)
|
||||||
|
|
||||||
|
const unsignedEvent: UnsignedEvent = {
|
||||||
|
pubkey: rawEvent.pubkey,
|
||||||
|
kind: kinds.RelayList,
|
||||||
|
tags: [...nonRelayTags, ...relayTags],
|
||||||
|
content: rawEvent.content,
|
||||||
|
created_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(true)
|
||||||
|
|
||||||
|
const signedEvent = await window.nostr
|
||||||
|
?.signEvent(unsignedEvent)
|
||||||
|
.then((event) => event as Event)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to sign the event!')
|
||||||
|
log(true, LogType.Error, 'Failed to sign the event!', err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!signedEvent) {
|
||||||
|
setIsPublishing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishedOnRelays =
|
||||||
|
await RelayController.getInstance().publishOnRelays(
|
||||||
|
signedEvent,
|
||||||
|
ndkRelayList.writeRelayUrls
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle cases where publishing failed or succeeded
|
||||||
|
if (publishedOnRelays.length === 0) {
|
||||||
|
toast.error('Failed to publish relay list event on any relay')
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
|
||||||
|
'\n'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const newNDKRelayList = new NDKRelayList(ndkRelayList.ndk, signedEvent)
|
||||||
|
setNDKRelayList(newNDKRelayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeRelayType = async (
|
||||||
|
relayUrl: string,
|
||||||
|
relayType: UserRelaysType
|
||||||
|
) => {
|
||||||
|
if (!ndkRelayList) return
|
||||||
|
|
||||||
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
|
|
||||||
|
const rawEvent = ndkRelayList.rawEvent()
|
||||||
|
|
||||||
|
const nonRelayTags = rawEvent.tags.filter(
|
||||||
|
(tag) => tag[0] !== 'r' && tag[0] !== 'relay'
|
||||||
|
)
|
||||||
|
|
||||||
|
// all relay tags except the changing one
|
||||||
|
const relayTags = rawEvent.tags
|
||||||
|
.filter((tag) => tag[0] === 'r' || tag[0] === 'relay')
|
||||||
|
.filter((tag) => normalizeWebSocketURL(tag[1]) !== normalizedUrl)
|
||||||
|
|
||||||
|
// create a new relay tag
|
||||||
|
const tag = ['r', normalizedUrl]
|
||||||
|
|
||||||
|
// set the relay marker
|
||||||
|
if (relayType !== UserRelaysType.Both) {
|
||||||
|
tag.push(relayType === UserRelaysType.Read ? READ_MARKER : WRITE_MARKER)
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsignedEvent: UnsignedEvent = {
|
||||||
|
pubkey: rawEvent.pubkey,
|
||||||
|
kind: kinds.RelayList,
|
||||||
|
tags: [...nonRelayTags, ...relayTags, tag],
|
||||||
|
content: rawEvent.content,
|
||||||
|
created_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(true)
|
||||||
|
|
||||||
|
const signedEvent = await window.nostr
|
||||||
|
?.signEvent(unsignedEvent)
|
||||||
|
.then((event) => event as Event)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to sign the event!')
|
||||||
|
log(true, LogType.Error, 'Failed to sign the event!', err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!signedEvent) {
|
||||||
|
setIsPublishing(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishedOnRelays =
|
||||||
|
await RelayController.getInstance().publishOnRelays(
|
||||||
|
signedEvent,
|
||||||
|
ndkRelayList.writeRelayUrls
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle cases where publishing failed or succeeded
|
||||||
|
if (publishedOnRelays.length === 0) {
|
||||||
|
toast.error('Failed to publish relay list event on any relay')
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
`Event published successfully to the following relays\n\n${publishedOnRelays.join(
|
||||||
|
'\n'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const newNDKRelayList = new NDKRelayList(ndkRelayList.ndk, signedEvent)
|
||||||
|
setNDKRelayList(newNDKRelayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ndkRelayList)
|
||||||
|
return <div>Could not fetch user relay list or user is not logged in </div>
|
||||||
|
|
||||||
|
const relayMap = new Map<string, UserRelaysType>()
|
||||||
|
|
||||||
|
ndkRelayList.readRelayUrls.forEach((relayUrl) => {
|
||||||
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
|
|
||||||
|
if (!relayMap.has(normalizedUrl)) {
|
||||||
|
relayMap.set(normalizedUrl, UserRelaysType.Read)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ndkRelayList.writeRelayUrls.forEach((relayUrl) => {
|
||||||
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
|
|
||||||
|
if (relayMap.has(normalizedUrl)) {
|
||||||
|
relayMap.set(normalizedUrl, UserRelaysType.Both)
|
||||||
|
} else {
|
||||||
|
relayMap.set(normalizedUrl, UserRelaysType.Write)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const relayEntries = Array.from(relayMap.entries())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{isPublishing && <LoadingSpinner desc='Publishing relay list event' />}
|
||||||
<div className='IBMSMSplitMainFullSideFWMid'>
|
<div className='IBMSMSplitMainFullSideFWMid'>
|
||||||
<div className='IBMSMSplitMainFullSideSec'>
|
<div className='IBMSMSplitMainFullSideSec'>
|
||||||
<div className='relayList'>
|
<div className='relayList'>
|
||||||
<div className='inputLabelWrapperMain'>
|
<div className='inputLabelWrapperMain'>
|
||||||
<label className='form-label labelMain'>Your relays</label>
|
<label className='form-label labelMain'>Your relays</label>
|
||||||
</div>
|
</div>
|
||||||
{usersRelays.map((relay, index) => (
|
{relayEntries.map(([relayUrl, relayType]) => (
|
||||||
<RelayListItem key={index} item={relay} isOwnRelay />
|
<RelayListItem
|
||||||
|
key={relayUrl}
|
||||||
|
relayUrl={relayUrl}
|
||||||
|
relayType={relayType}
|
||||||
|
isOwnRelay
|
||||||
|
handleAdd={handleAdd}
|
||||||
|
handleRemove={handleRemove}
|
||||||
|
changeRelayType={changeRelayType}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -20,11 +291,15 @@ export const RelaySettings = () => {
|
|||||||
placeholder='wss://some-relay.com'
|
placeholder='wss://some-relay.com'
|
||||||
type='text'
|
type='text'
|
||||||
name='relay'
|
name='relay'
|
||||||
value=''
|
value={inputValue}
|
||||||
onChange={() => {}}
|
onChange={(_, value) => setInputValue(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button className='btn btnMain' type='button'>
|
<button
|
||||||
|
className='btn btnMain'
|
||||||
|
type='button'
|
||||||
|
onClick={() => handleAdd(inputValue).then(() => setInputValue(''))}
|
||||||
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -38,9 +313,21 @@ export const RelaySettings = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='relayList'>
|
<div className='relayList'>
|
||||||
{degmodsRelays.map((relay, index) => (
|
{degmodRelays.map((relayUrl) => {
|
||||||
<RelayListItem key={index} item={relay} />
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
))}
|
const alreadyAdded = relayMap.has(normalizedUrl)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RelayListItem
|
||||||
|
key={relayUrl}
|
||||||
|
relayUrl={normalizedUrl}
|
||||||
|
alreadyAdded={alreadyAdded}
|
||||||
|
handleAdd={handleAdd}
|
||||||
|
handleRemove={handleRemove}
|
||||||
|
changeRelayType={changeRelayType}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -53,35 +340,76 @@ export const RelaySettings = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='relayList'>
|
<div className='relayList'>
|
||||||
{recommendRelays.map((relay, index) => (
|
{recommendRelays.map((relayUrl) => {
|
||||||
<RelayListItem key={index} item={relay} />
|
const normalizedUrl = normalizeWebSocketURL(relayUrl)
|
||||||
))}
|
const alreadyAdded = relayMap.has(normalizedUrl)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RelayListItem
|
||||||
|
key={relayUrl}
|
||||||
|
relayUrl={normalizedUrl}
|
||||||
|
alreadyAdded={alreadyAdded}
|
||||||
|
handleAdd={handleAdd}
|
||||||
|
handleRemove={handleRemove}
|
||||||
|
changeRelayType={changeRelayType}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelayListItem = ({
|
type RelayItemProps = {
|
||||||
item,
|
relayUrl: string
|
||||||
isOwnRelay
|
relayType?: UserRelaysType
|
||||||
}: {
|
|
||||||
item: RelayItem
|
|
||||||
isOwnRelay?: boolean
|
isOwnRelay?: boolean
|
||||||
}) => {
|
alreadyAdded?: boolean
|
||||||
|
handleAdd: (relayUrl: string) => void
|
||||||
|
handleRemove: (relayUrl: string) => void
|
||||||
|
changeRelayType: (relayUrl: string, relayType: UserRelaysType) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RelayListItem = ({
|
||||||
|
relayUrl,
|
||||||
|
relayType,
|
||||||
|
isOwnRelay,
|
||||||
|
alreadyAdded,
|
||||||
|
handleAdd,
|
||||||
|
handleRemove,
|
||||||
|
changeRelayType
|
||||||
|
}: RelayItemProps) => {
|
||||||
|
const [isConnected, setIsConnected] = useState(false)
|
||||||
|
|
||||||
|
useDidMount(() => {
|
||||||
|
RelayController.getInstance()
|
||||||
|
.connectRelay(relayUrl)
|
||||||
|
.then((relay) => {
|
||||||
|
if (relay && relay.connected) {
|
||||||
|
setIsConnected(true)
|
||||||
|
} else {
|
||||||
|
setIsConnected(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relayListItem'>
|
<div className='relayListItem'>
|
||||||
<div className='relayListItemSec relayListItemSecPic'>
|
<div className='relayListItemSec relayListItemSecPic'>
|
||||||
<div
|
<div
|
||||||
className='relayListItemSecPicImg'
|
className='relayListItemSecPicImg'
|
||||||
style={{
|
style={{
|
||||||
background: item.backgroundColor
|
background: isConnected ? '#60ae60' : '#cd4d45'
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className='relayListItemSec relayListItemSecDetails'>
|
<div className='relayListItemSec relayListItemSecDetails'>
|
||||||
<p className='relayListItemSecDetailsText'>{item.url}</p>
|
<p className='relayListItemSecDetailsText'>{relayUrl}</p>
|
||||||
<div className='relayListItemSecDetailsExtra'>
|
<div className='relayListItemSecDetailsExtra'>
|
||||||
|
{(relayType === UserRelaysType.Read ||
|
||||||
|
relayType === UserRelaysType.Both) && (
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='-64 0 512 512'
|
viewBox='-64 0 512 512'
|
||||||
@ -90,10 +418,14 @@ const RelayListItem = ({
|
|||||||
fill='currentColor'
|
fill='currentColor'
|
||||||
data-bs-toggle='tooltip'
|
data-bs-toggle='tooltip'
|
||||||
data-bss-tooltip
|
data-bss-tooltip
|
||||||
aria-label={item.readTitle}
|
aria-label='Read'
|
||||||
>
|
>
|
||||||
<path d='M0 64C0 28.65 28.65 0 64 0H224V128C224 145.7 238.3 160 256 160H384V448C384 483.3 355.3 512 320 512H64C28.65 512 0 483.3 0 448V64zM256 128V0L384 128H256z'></path>
|
<path d='M0 64C0 28.65 28.65 0 64 0H224V128C224 145.7 238.3 160 256 160H384V448C384 483.3 355.3 512 320 512H64C28.65 512 0 483.3 0 448V64zM256 128V0L384 128H256z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(relayType === UserRelaysType.Write ||
|
||||||
|
relayType === UserRelaysType.Both) && (
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 -32 576 576'
|
viewBox='0 -32 576 576'
|
||||||
@ -102,23 +434,10 @@ const RelayListItem = ({
|
|||||||
fill='currentColor'
|
fill='currentColor'
|
||||||
data-bs-toggle='tooltip'
|
data-bs-toggle='tooltip'
|
||||||
data-bss-tooltip
|
data-bss-tooltip
|
||||||
aria-label={item.writeTitle}
|
aria-label='Write'
|
||||||
>
|
>
|
||||||
<path d='M0 64C0 28.65 28.65 0 64 0H224V128C224 145.7 238.3 160 256 160H384V299.6L289.3 394.3C281.1 402.5 275.3 412.8 272.5 424.1L257.4 484.2C255.1 493.6 255.7 503.2 258.8 512H64C28.65 512 0 483.3 0 448V64zM256 128V0L384 128H256zM564.1 250.1C579.8 265.7 579.8 291 564.1 306.7L534.7 336.1L463.8 265.1L493.2 235.7C508.8 220.1 534.1 220.1 549.8 235.7L564.1 250.1zM311.9 416.1L441.1 287.8L512.1 358.7L382.9 487.9C378.8 492 373.6 494.9 368 496.3L307.9 511.4C302.4 512.7 296.7 511.1 292.7 507.2C288.7 503.2 287.1 497.4 288.5 491.1L303.5 431.8C304.9 426.2 307.8 421.1 311.9 416.1V416.1z'></path>
|
<path d='M0 64C0 28.65 28.65 0 64 0H224V128C224 145.7 238.3 160 256 160H384V299.6L289.3 394.3C281.1 402.5 275.3 412.8 272.5 424.1L257.4 484.2C255.1 493.6 255.7 503.2 258.8 512H64C28.65 512 0 483.3 0 448V64zM256 128V0L384 128H256zM564.1 250.1C579.8 265.7 579.8 291 564.1 306.7L534.7 336.1L463.8 265.1L493.2 235.7C508.8 220.1 534.1 220.1 549.8 235.7L564.1 250.1zM311.9 416.1L441.1 287.8L512.1 358.7L382.9 487.9C378.8 492 373.6 494.9 368 496.3L307.9 511.4C302.4 512.7 296.7 511.1 292.7 507.2C288.7 503.2 287.1 497.4 288.5 491.1L303.5 431.8C304.9 426.2 307.8 421.1 311.9 416.1V416.1z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
{item.subscribedTitle && (
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='0 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
data-bs-toggle='tooltip'
|
|
||||||
data-bss-tooltip
|
|
||||||
aria-label={item.subscribedTitle}
|
|
||||||
>
|
|
||||||
<path d='M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zm-141.651-35.33c4.937-32.999-20.191-50.739-54.55-62.573l11.146-44.702-27.213-6.781-10.851 43.524c-7.154-1.783-14.502-3.464-21.803-5.13l10.929-43.81-27.198-6.781-11.153 44.686c-5.922-1.349-11.735-2.682-17.377-4.084l.031-.14-37.53-9.37-7.239 29.062s20.191 4.627 19.765 4.913c11.022 2.751 13.014 10.044 12.68 15.825l-12.696 50.925c.76.194 1.744.473 2.829.907-.907-.225-1.876-.473-2.876-.713l-17.796 71.338c-1.349 3.348-4.767 8.37-12.471 6.464.271.395-19.78-4.937-19.78-4.937l-13.51 31.147 35.414 8.827c6.588 1.651 13.045 3.379 19.4 5.006l-11.262 45.213 27.182 6.781 11.153-44.733a1038.209 1038.209 0 0 0 21.687 5.627l-11.115 44.523 27.213 6.781 11.262-45.128c46.404 8.781 81.299 5.239 95.986-36.727 11.836-33.79-.589-53.281-25.004-65.991 17.78-4.098 31.174-15.792 34.747-39.949zm-62.177 87.179c-8.41 33.79-65.308 15.523-83.755 10.943l14.944-59.899c18.446 4.603 77.6 13.717 68.811 48.956zm8.417-87.667c-7.673 30.736-55.031 15.12-70.393 11.292l13.548-54.327c15.363 3.828 64.836 10.973 56.845 43.035z'></path>
|
|
||||||
</svg>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,17 +467,72 @@ const RelayListItem = ({
|
|||||||
className='dropdown-menu dropdownMainMenu'
|
className='dropdown-menu dropdownMainMenu'
|
||||||
style={{ position: 'absolute' }}
|
style={{ position: 'absolute' }}
|
||||||
>
|
>
|
||||||
<a className='dropdown-item dropdownMainMenuItem' href='#'>
|
{(relayType === UserRelaysType.Read ||
|
||||||
|
relayType === UserRelaysType.Write) && (
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() => changeRelayType(relayUrl, UserRelaysType.Both)}
|
||||||
|
>
|
||||||
|
Read & Write
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{relayType === UserRelaysType.Both && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
changeRelayType(relayUrl, UserRelaysType.Read)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Read Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
changeRelayType(relayUrl, UserRelaysType.Write)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Write Only
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{relayType === UserRelaysType.Read && (
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
changeRelayType(relayUrl, UserRelaysType.Write)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Write Only
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{relayType === UserRelaysType.Write && (
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() => changeRelayType(relayUrl, UserRelaysType.Read)}
|
||||||
|
>
|
||||||
|
Read Only
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() => handleRemove(relayUrl)}
|
||||||
|
>
|
||||||
Remove
|
Remove
|
||||||
</a>
|
</div>
|
||||||
<a className='dropdown-item dropdownMainMenuItem' href='#'>
|
|
||||||
Details
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isOwnRelay && (
|
{!isOwnRelay && !alreadyAdded && (
|
||||||
<button className='btn btnMain' type='button'>
|
<button
|
||||||
|
className='btn btnMain'
|
||||||
|
type='button'
|
||||||
|
onClick={() => handleAdd(relayUrl)}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='-32 0 512 512'
|
viewBox='-32 0 512 512'
|
||||||
@ -175,68 +549,6 @@ const RelayListItem = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RelayItem {
|
const degmodRelays = ['wss://relay.degmods.com']
|
||||||
url: string
|
|
||||||
backgroundColor: string
|
|
||||||
readTitle: string
|
|
||||||
writeTitle: string
|
|
||||||
subscribedTitle: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersRelays: RelayItem[] = [
|
const recommendRelays = ['wss://relay.degmods.com']
|
||||||
{
|
|
||||||
url: 'wss://relay.wibblywobbly.com',
|
|
||||||
backgroundColor: '#cd4d45',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'wss://relay.wibblywobbly.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: 'Paid (Subscribed)'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'wss://relay.degmods.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const degmodsRelays: RelayItem[] = [
|
|
||||||
{
|
|
||||||
url: 'wss://relay1.degmods.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'wss://relay2.degmods.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const recommendRelays: RelayItem[] = [
|
|
||||||
{
|
|
||||||
url: 'wss://relay1.degmods.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'wss://relay2.degmods.com',
|
|
||||||
backgroundColor: '#60ae60',
|
|
||||||
readTitle: 'Read',
|
|
||||||
writeTitle: 'Write',
|
|
||||||
subscribedTitle: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
Loading…
Reference in New Issue
Block a user