Re-work relays page

This commit is contained in:
Jon Staab
2024-10-03 11:49:52 -07:00
parent 9834a31511
commit 73a9b46f91
12 changed files with 259 additions and 91 deletions

View File

@@ -51,16 +51,20 @@
--secondary: oklch(var(--s));
}
.bg-alt, .bg-alt .bg-alt .bg-alt {
@apply bg-base-100;
}
.bg-alt .bg-alt, .bg-alt .bg-alt .bg-alt .bg-alt {
@apply bg-base-300;
}
.card2 {
@apply text-ellipsis rounded-box bg-base-100 p-6 text-base-content;
@apply text-ellipsis rounded-box p-6 text-base-content;
}
.card2.card2-sm {
@apply rounded-box bg-base-100 p-4 text-base-content;
}
.card2.card2-alt {
@apply bg-base-300;
@apply p-4 text-base-content;
}
.column {

View File

@@ -1,5 +1,5 @@
import {uniqBy, sleep, chunk, equals, choice} from "@welshman/lib"
import {DELETE, REACTION, getPubkeyTagValues, createEvent, displayProfile} from "@welshman/util"
import {uniqBy, sleep, chunk, equals, choice, append} from "@welshman/lib"
import {DELETE, MUTES, FOLLOWS, REACTION, getPubkeyTagValues, createEvent, displayProfile} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {
@@ -15,6 +15,7 @@ import {
loadMutes,
getFollows,
tagEvent,
tagPubkey,
tagReactionTo,
} from "@welshman/app"
import {tagRoom, MEMBERSHIPS, INDEXER_RELAYS} from "@app/state"
@@ -110,6 +111,19 @@ export const removeSpaceMembership = (url: string) =>
export const removeRoomMembership = (url: string, room: string) =>
updateList(MEMBERSHIPS, (tags: string[][]) => tags.filter(t => !equals(tagRoom(room, url), t)))
export const unfollowPerson = (pubkey: string) =>
updateList(FOLLOWS, tags => tags.filter(t => t[1] !== pubkey))
export const followPerson = (pubkey: string) =>
updateList(FOLLOWS, tags => append(tagPubkey(pubkey), tags))
export const unmutePerson = (pubkey: string) =>
updateList(MUTES, tags => tags.filter(t => t[1] !== pubkey))
export const mutePerson = (pubkey: string) =>
updateList(MUTES, tags => append(tagPubkey(pubkey), tags))
// Actions
export const publishReaction = ({relays, event, content, tags = []}: {

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import {onMount} from 'svelte'
import {getAddress, Address} from "@welshman/util"
import Spinner from "@lib/components/Spinner.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
@@ -13,25 +12,18 @@
const event = deriveEvent(idOrAddress, relays)
let element: Element
let bgClass = "bg-base-300"
$: address = $event ? getAddress($event) : ""
$: isGroup = address.match(/^(34550|35834):/)
onMount(() => {
if (element.closest('.bg-base-300')) {
bgClass = 'bg-base-100'
}
})
</script>
<button class="block text-left my-2 max-w-full" bind:this={element} on:click|stopPropagation>
{#if $event}
<NoteCard event={$event} class="p-4 rounded-box {bgClass}">
<NoteCard event={$event} class="p-4 rounded-box bg-alt">
<slot name="note-content" event={$event} {depth} />
</NoteCard>
{:else}
<div class="p-4 rounded-box {bgClass}">
<div class="p-4 rounded-box">
<Spinner loading>Loading event...</Spinner>
</div>
{/if}

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import {onMount} from 'svelte'
import type {Readable} from 'svelte/store'
import {relaySearch} from "@welshman/app"
import {createScroller} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import RelayItem from "@app/components/RelayItem.svelte"
import {discoverRelays} from "@app/state"
export let mode: string
export let relays: Readable<string[]>
const addRelay = (url: string) => null
let term = ""
let limit = 20
let element: Element
onMount(() => {
const sub = discoverRelays()
const scroller = createScroller({
delay: 300,
element: element.closest('.modal-box')!,
onScroll: () => {
limit += 20
},
})
return () => {
sub.close()
scroller.stop()
}
})
</script>
<div class="column gap-2" bind:this={element}>
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="magnifer" />
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
{#each $relaySearch.searchValues(term).filter(url => !$relays.includes(url)).slice(0, limit) as url (url)}
<RelayItem {url}>
<Button class="btn btn-outline btn-sm flex items-center" on:click={() => addRelay(url)}>
<Icon icon="add-circle" />
Add Relay
</Button>
</RelayItem>
{/each}
</div>

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import {displayUrl} from '@welshman/lib'
import {displayRelayUrl} from '@welshman/util'
import {deriveRelay} from '@welshman/app'
export let url
const relay = deriveRelay(url)
$: connections = $relay?.stats?.connect_count || 0
</script>
<div class="card2 card2-sm bg-alt column gap-2">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon="remote-controller-minimalistic" />
{displayRelayUrl(url)}
</div>
<slot />
</div>
{#if $relay?.profile?.description}
<p>{$relay?.profile.description}</p>
{/if}
<span class="flex items-center gap-1 text-sm">
{#if $relay?.profile?.contact}
<Link external class="underline" href={$relay.profile.contact}
>{displayUrl($relay.profile.contact)}</Link>
&bull;
{/if}
{#if $relay?.profile?.supported_nips}
<span class="cursor-pointer underline tooltip" data-tip="NIPs supported: {$relay.profile.supported_nips.join(", ")}">
{$relay.profile.supported_nips.length} NIPs
</span>
&bull;
{/if}
Connected {connections} {connections === 1 ? 'time' : 'times'}
</span>
</div>

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 15L12 9L5 15" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M2 13H5.16026C6.06543 13 6.51802 13 6.91584 13.183C7.31367 13.3659 7.60821 13.7096 8.19729 14.3968L8.80271 15.1032C9.39179 15.7904 9.68633 16.1341 10.0842 16.317C10.482 16.5 10.9346 16.5 11.8397 16.5H12.1603C13.0654 16.5 13.518 16.5 13.9158 16.317C14.3137 16.1341 14.6082 15.7904 15.1973 15.1032L15.8027 14.3968C16.3918 13.7096 16.6863 13.3659 17.0842 13.183C17.482 13 17.9346 13 18.8397 13H22" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 873 B

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 22V20M14.5 22V20" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M11 20V20.75H11.75V20H11ZM14 19.25C13.5858 19.25 13.25 19.5858 13.25 20C13.25 20.4142 13.5858 20.75 14 20.75V19.25ZM17.5 5.25C17.0858 5.25 16.75 5.58579 16.75 6C16.75 6.41421 17.0858 6.75 17.5 6.75V5.25ZM7 5.25C6.58579 5.25 6.25 5.58579 6.25 6C6.25 6.41421 6.58579 6.75 7 6.75V5.25ZM9 19.25C8.58579 19.25 8.25 19.5858 8.25 20C8.25 20.4142 8.58579 20.75 9 20.75V19.25ZM15 20.75C15.4142 20.75 15.75 20.4142 15.75 20C15.75 19.5858 15.4142 19.25 15 19.25V20.75ZM10.25 11.25V20H11.75V11.25H10.25ZM11 19.25H4.23256V20.75H11V19.25ZM2.75 17.3953V11.25H1.25V17.3953H2.75ZM4.23256 19.25C3.51806 19.25 2.75 18.5323 2.75 17.3953H1.25C1.25 19.1354 2.48104 20.75 4.23256 20.75V19.25ZM6.5 6.75C8.46677 6.75 10.25 8.65209 10.25 11.25H11.75C11.75 8.04892 9.50379 5.25 6.5 5.25V6.75ZM6.5 5.25C3.49621 5.25 1.25 8.04892 1.25 11.25H2.75C2.75 8.65209 4.53323 6.75 6.5 6.75V5.25ZM21.25 11.25V17.4253H22.75V11.25H21.25ZM19.7931 19.25H14V20.75H19.7931V19.25ZM21.25 17.4253C21.25 18.5457 20.4934 19.25 19.7931 19.25V20.75C21.5305 20.75 22.75 19.1488 22.75 17.4253H21.25ZM22.75 11.25C22.75 8.04892 20.5038 5.25 17.5 5.25V6.75C19.4668 6.75 21.25 8.65209 21.25 11.25H22.75ZM7 6.75H18V5.25H7V6.75ZM9 20.75H15V19.25H9V20.75Z" fill="#1C274C"/>
<path d="M5 16H8" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M16 9.88432V5.41121M16 5.41121V2.63519C16 2.39905 16.1676 2.19612 16.3994 2.15144L16.8855 2.05779C17.4738 1.94443 18.0821 1.99855 18.6412 2.214L18.7203 2.24451C19.2746 2.4581 19.8807 2.498 20.4582 2.35891C20.7343 2.2924 21 2.50168 21 2.78573V5.00723C21 5.2442 20.8376 5.45031 20.6073 5.5058L20.5407 5.52184C19.9095 5.67387 19.247 5.63026 18.6412 5.39679C18.0821 5.18135 17.4738 5.12722 16.8855 5.24058L16 5.41121Z" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import {slide} from '@lib/transition'
import Icon from '@lib/components/Icon.svelte'
const toggle = () => {
isOpen = !isOpen
}
let isOpen = false
</script>
<div class="flex flex-col gap-4 relative {$$props.class}">
<button
type="button"
class="absolute top-8 right-8 cursor-pointer w-4 h-4 transition-all"
class:rotate-90={!isOpen}
on:click={toggle}>
<Icon icon="alt-arrow-down" />
</button>
<slot name="title" />
<slot name="description" />
{#if isOpen}
<div transition:slide>
<slot />
</div>
{/if}
</div>

View File

@@ -19,6 +19,7 @@
import AddCircle from "@assets/icons/Add Circle.svg?dataurl"
import AltArrowDown from "@assets/icons/Alt Arrow Down.svg?dataurl"
import AltArrowRight from "@assets/icons/Alt Arrow Right.svg?dataurl"
import AltArrowUp from "@assets/icons/Alt Arrow Up.svg?dataurl"
import AltArrowLeft from "@assets/icons/Alt Arrow Left.svg?dataurl"
import ArrowRight from "@assets/icons/Arrow Right.svg?dataurl"
import Bag from "@assets/icons/Bag.svg?dataurl"
@@ -41,6 +42,7 @@
import Hashtag from "@assets/icons/Hashtag.svg?dataurl"
import HandPills from "@assets/icons/Hand Pills.svg?dataurl"
import HomeSmile from "@assets/icons/Home Smile.svg?dataurl"
import Inbox from "@assets/icons/Inbox.svg?dataurl"
import InfoCircle from "@assets/icons/Info Circle.svg?dataurl"
import InfoSquare from "@assets/icons/Info Square.svg?dataurl"
import Key from "@assets/icons/Key.svg?dataurl"
@@ -48,6 +50,7 @@
import Login from "@assets/icons/Login.svg?dataurl"
import Login2 from "@assets/icons/Login 2.svg?dataurl"
import Magnifer from "@assets/icons/Magnifer.svg?dataurl"
import Mailbox from "@assets/icons/Mailbox.svg?dataurl"
import MapPoint from "@assets/icons/Map Point.svg?dataurl"
import MenuDots from "@assets/icons/Menu Dots.svg?dataurl"
import NotesMinimalistic from "@assets/icons/Notes Minimalistic.svg?dataurl"
@@ -86,6 +89,7 @@
"add-circle": AddCircle,
"alt-arrow-down": AltArrowDown,
"alt-arrow-right": AltArrowRight,
"alt-arrow-up": AltArrowUp,
"alt-arrow-left": AltArrowLeft,
"arrow-right": ArrowRight,
bag: Bag,
@@ -108,6 +112,7 @@
hashtag: Hashtag,
"hand-pills": HandPills,
"home-smile": HomeSmile,
"inbox": Inbox,
"info-circle": InfoCircle,
"info-square": InfoSquare,
key: Key,
@@ -115,6 +120,7 @@
login: Login,
"login-2": Login2,
magnifer: Magnifer,
mailbox: Mailbox,
"map-point": MapPoint,
"menu-dots": MenuDots,
"notes-minimalistic": NotesMinimalistic,

View File

@@ -5,6 +5,6 @@
export let props = {}
</script>
<div class="modal-box" transition:fly={{duration: 100}}>
<div class="modal-box bg-alt" transition:fly={{duration: 100}}>
<svelte:component this={component} {...props} />
</div>

View File

@@ -1,92 +1,114 @@
<script lang="ts">
import {onMount} from "svelte"
import {derived} from "svelte/store"
import {uniq} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import type {Readable} from 'svelte/store'
import {relaySearch, getRelayUrls, userRelaySelections, userInboxRelaySelections, getReadRelayUrls, getWriteRelayUrls} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import {INDEXER_RELAYS, discoverRelays} from "@app/state"
import Button from "@lib/components/Button.svelte"
import Collapse from "@lib/components/Collapse.svelte"
import RelayItem from "@app/components/RelayItem.svelte"
import RelayAdd from "@app/components/RelayAdd.svelte"
import {pushModal} from '@app/modal'
const readRelayUrls = derived(userRelaySelections, getReadRelayUrls)
const writeRelayUrls = derived(userRelaySelections, getWriteRelayUrls)
const inboxRelayUrls = derived(userInboxRelaySelections, getRelayUrls)
const removeRelay = (url: string) => null
const addRelay = (mode: string, relays: Readable<string[]>) =>
pushModal(RelayAdd, {mode, relays})
const addRelay = (url: string) => null
const removeReadRelay = (url: string) => null
let term = ""
let currentRelayUrls: string[] = []
const removeWriteRelay = (url: string) => null
$: currentRelayUrls = uniq([
...currentRelayUrls,
...getRelayUrls($userRelaySelections),
...getRelayUrls($userInboxRelaySelections),
]).sort()
onMount(() => {
const sub = discoverRelays()
return () => sub.close()
})
const removeInboxRelay = (url: string) => null
</script>
<div class="content column gap-4">
<h1 class="superheading mt-20">Relays</h1>
<p class="text-center">Get connected with the nostr network</p>
{#each currentRelayUrls as url}
{@const read = $readRelayUrls.includes(url)}
{@const write = $writeRelayUrls.includes(url)}
{@const inbox = $inboxRelayUrls.includes(url)}
<div class="card2 card2-sm flex flex-col gap-2 overflow-visible">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon="remote-controller-minimalistic" />
{displayRelayUrl(url)}
</div>
<Button class="flex items-center" on:click={() => removeRelay(url)}>
<Icon icon="close-circle" />
</Button>
</div>
<div class="flex gap-3">
<div
class="tooltip tooltip-right"
data-tip="Notes for you will {read ? '' : 'not'} be sent here.">
<Button class="btn btn-sm btn-{read ? 'primary' : 'neutral'}">
Read
<Collapse class="card2 bg-alt column gap-4">
<h2 slot="title" class="text-xl flex items-center gap-3">
<Icon icon="earth" />
Broadcast Relays
</h2>
<p slot="description" class="text-sm">
These relays will be advertised on your profile as places where you send your public
notes. Be sure to select relays that will accept your notes, and which will let people
who follow you read them.
</p>
<div class="column gap-2">
{#each $writeRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="flex items-center tooltip"
data-tip="Stop using this relay"
on:click={() => removeWriteRelay(url)}>
<Icon icon="close-circle" />
</Button>
</div>
<div
class="tooltip tooltip-right"
data-tip="Notes you publish will {write ? '' : 'not'} be sent here.">
<Button class="btn btn-sm btn-{write ? 'primary' : 'neutral'}">
Write
</Button>
</div>
<div
class="tooltip tooltip-right"
data-tip="Direct messages will {inbox ? '' : 'not'} be sent here.">
<Button class="btn btn-sm btn-{inbox ? 'primary' : 'neutral'}">
Inbox
</Button>
</div>
</div>
</div>
{/each}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="magnifer" />
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
{#each $relaySearch.searchValues(term).filter(url => !currentRelayUrls.includes(url)) as url (url)}
<div class="card2 card2-sm flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon="remote-controller-minimalistic" />
{displayRelayUrl(url)}
</div>
<Button class="flex items-center" on:click={() => addRelay(url)}>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" on:click={() => addRelay('write', writeRelayUrls)}>
<Icon icon="add-circle" />
Add Relay
</Button>
</div>
{/each}
</Collapse>
<Collapse class="card2 bg-alt column gap-4">
<h2 slot="title" class="text-xl flex items-center gap-3">
<Icon icon="inbox" />
Inbox Relays
</h2>
<p slot="description" class="text-sm">
These relays will be advertised on your profile as places where other people should
send notes intended for you. Be sure to select relays that will accept notes that
tag you.
</p>
<div class="column gap-2">
{#each $readRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="flex items-center tooltip"
data-tip="Stop using this relay"
on:click={() => removeReadRelay(url)}>
<Icon icon="close-circle" />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" on:click={() => addRelay('read', readRelayUrls)}>
<Icon icon="add-circle" />
Add Relay
</Button>
</div>
</Collapse>
<Collapse class="card2 bg-alt column gap-4">
<h2 slot="title" class="text-xl flex items-center gap-3">
<Icon icon="mailbox" />
Messaging Relays
</h2>
<p slot="description" class="text-sm">
These relays will be advertised on your profile as places you use to send and
receive direct messages. Be sure to select relays that will accept your messages
and messages from people you'd like to be in contact with.
</p>
<div class="column gap-2">
{#each $inboxRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="flex items-center tooltip"
data-tip="Stop using this relay"
on:click={() => removeInboxRelay(url)}>
<Icon icon="close-circle" />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" on:click={() => addRelay('inbox', inboxRelayUrls)}>
<Icon icon="add-circle" />
Add Relay
</Button>
</div>
</Collapse>
</div>