forked from coracle/flotilla
Overhaul relay settings page
This commit is contained in:
@@ -1,30 +1,70 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
|
import {SvelteSet} from "svelte/reactivity"
|
||||||
import type {Readable} from "svelte/store"
|
import type {Readable} from "svelte/store"
|
||||||
import {tryCatch} from "@welshman/lib"
|
import {tryCatch} from "@welshman/lib"
|
||||||
import {isShareableRelayUrl, normalizeRelayUrl} from "@welshman/util"
|
import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util"
|
||||||
import {relaySearch} from "@welshman/app"
|
import type {Thunk} from "@welshman/app"
|
||||||
|
import {waitForThunkError, relaySearch} from "@welshman/app"
|
||||||
import {createScroller} from "@lib/html"
|
import {createScroller} from "@lib/html"
|
||||||
|
import {errorMessage} from "@lib/util"
|
||||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Modal from "@lib/components/Modal.svelte"
|
import Modal from "@lib/components/Modal.svelte"
|
||||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import RelayItem from "@app/components/RelayItem.svelte"
|
import RelayItem from "@app/components/RelayItem.svelte"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
relays: Readable<string[]>
|
relays: Readable<string[]>
|
||||||
addRelay: (url: string) => void
|
addRelay: (url: string) => Promise<Thunk>
|
||||||
|
matchRelay?: (url: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const {relays, addRelay}: Props = $props()
|
const {relays, addRelay, matchRelay}: Props = $props()
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const customUrl = $derived(tryCatch(() => normalizeRelayUrl(term)))
|
||||||
|
|
||||||
|
const add = async (url: string) => {
|
||||||
|
loading.add(url)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const error = await waitForThunkError(await addRelay(url))
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: `Failed to add relay: ${errorMessage(error)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.delete(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let term = $state("")
|
let term = $state("")
|
||||||
let limit = $state(20)
|
let limit = $state(20)
|
||||||
let element: Element | undefined = $state()
|
let element: Element | undefined = $state()
|
||||||
|
|
||||||
const customUrl = $derived(tryCatch(() => normalizeRelayUrl(term)))
|
const loading = $state(new SvelteSet<string>())
|
||||||
|
|
||||||
|
const searchResults = $derived(
|
||||||
|
$relaySearch
|
||||||
|
.searchValues(term)
|
||||||
|
.filter(url => {
|
||||||
|
if (matchRelay?.(url) === false) return false
|
||||||
|
if ($relays.includes(url)) return false
|
||||||
|
if (isIPAddress(url)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.slice(0, limit),
|
||||||
|
)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const scroller = createScroller({
|
const scroller = createScroller({
|
||||||
@@ -52,23 +92,35 @@
|
|||||||
<RelayItem url={term}>
|
<RelayItem url={term}>
|
||||||
<Button
|
<Button
|
||||||
class="btn btn-outline btn-sm flex items-center"
|
class="btn btn-outline btn-sm flex items-center"
|
||||||
onclick={() => addRelay(customUrl)}>
|
disabled={loading.has(customUrl)}
|
||||||
<Icon icon={AddCircle} />
|
onclick={() => add(customUrl)}>
|
||||||
|
{#if loading.has(customUrl)}
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
{:else}
|
||||||
|
<Icon icon={AddCircle} />
|
||||||
|
{/if}
|
||||||
Add Relay
|
Add Relay
|
||||||
</Button>
|
</Button>
|
||||||
</RelayItem>
|
</RelayItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $relaySearch
|
{#each searchResults as url (url)}
|
||||||
.searchValues(term)
|
|
||||||
.filter(url => !$relays.includes(url))
|
|
||||||
.slice(0, limit) as url (url)}
|
|
||||||
<RelayItem {url}>
|
<RelayItem {url}>
|
||||||
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}>
|
<Button
|
||||||
<Icon icon={AddCircle} />
|
class="btn btn-outline btn-sm flex items-center"
|
||||||
|
disabled={loading.has(url)}
|
||||||
|
onclick={() => add(url)}>
|
||||||
|
{#if loading.has(url)}
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
{:else}
|
||||||
|
<Icon icon={AddCircle} />
|
||||||
|
{/if}
|
||||||
Add Relay
|
Add Relay
|
||||||
</Button>
|
</Button>
|
||||||
</RelayItem>
|
</RelayItem>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-primary flex-grow" onclick={back}>Done</Button>
|
||||||
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import {SvelteSet} from "svelte/reactivity"
|
||||||
|
import {waitForThunkError} from "@welshman/app"
|
||||||
|
import type {Thunk} from "@welshman/app"
|
||||||
|
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
|
import {errorMessage} from "@lib/util"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Modal from "@lib/components/Modal.svelte"
|
||||||
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import RelayAdd from "@app/components/RelayAdd.svelte"
|
||||||
|
import RelayItem from "@app/components/RelayItem.svelte"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
relays: Readable<string[]>
|
||||||
|
addRelay: (url: string) => Promise<Thunk>
|
||||||
|
removeRelay: (url: string) => Promise<Thunk>
|
||||||
|
matchRelay?: (url: string) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const {title, subtitle, relays, addRelay, removeRelay, matchRelay}: Props = $props()
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const add = () => pushModal(RelayAdd, {relays, addRelay, matchRelay})
|
||||||
|
|
||||||
|
const remove = async (url: string) => {
|
||||||
|
loading.add(url)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const error = await waitForThunkError(await removeRelay(url))
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: `Failed to remove relay: ${errorMessage(error)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.delete(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = $state(new SvelteSet<string>())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal>
|
||||||
|
<ModalBody>
|
||||||
|
<h2 class="text-xl">{title}</h2>
|
||||||
|
<p class="text-sm">{subtitle}</p>
|
||||||
|
{#each $relays.toSorted() as url (url)}
|
||||||
|
<RelayItem {url}>
|
||||||
|
<Button
|
||||||
|
class="btn btn-sm btn-neutral"
|
||||||
|
disabled={loading.has(url)}
|
||||||
|
onclick={() => remove(url)}>
|
||||||
|
{#if loading.has(url)}
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
{:else}
|
||||||
|
<Icon icon={CloseCircle} />
|
||||||
|
{/if}
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</RelayItem>
|
||||||
|
{/each}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon={AltArrowLeft} />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-primary" onclick={add}>
|
||||||
|
Add Relays
|
||||||
|
<Icon icon={AltArrowRight} />
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
export type ActionItem = {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
action: string
|
||||||
|
apply: () => unknown
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Stars from "@assets/icons/stars.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
|
||||||
|
const {title, action, subtitle, apply}: ActionItem = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card2 card2-sm bg-alt flex justify-between">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<strong>{title}</strong>
|
||||||
|
<p class="text-sm">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
<Button class="btn btn-neutral btn-sm" onclick={apply}>
|
||||||
|
<Icon icon={Stars} />
|
||||||
|
{action}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import Stars from "@assets/icons/stars.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Modal from "@lib/components/Modal.svelte"
|
||||||
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import type {ActionItem} from "@app/components/RelaySettingsActionItem.svelte"
|
||||||
|
import RelaySettingsActionItem from "@app/components/RelaySettingsActionItem.svelte"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
actionItems: Readable<ActionItem[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const {actionItems}: Props = $props()
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const applyAll = () => {
|
||||||
|
for (const actionItem of $actionItems) {
|
||||||
|
actionItem.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($actionItems.length === 0) {
|
||||||
|
back()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal>
|
||||||
|
<ModalBody>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<Icon icon={DangerTriangle} />
|
||||||
|
<strong class="text-lg">Action Items</strong>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm">
|
||||||
|
Below are recommendations for adjustments to your relay selections that you might consider.
|
||||||
|
</p>
|
||||||
|
{#each $actionItems as actionItem}
|
||||||
|
<RelaySettingsActionItem {...actionItem} />
|
||||||
|
{/each}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon={AltArrowLeft} />
|
||||||
|
Go Back
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-primary" onclick={applyAll}>
|
||||||
|
<Icon icon={Stars} />
|
||||||
|
Apply All Recommendations
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import Check from "@assets/icons/check.svg?dataurl"
|
||||||
|
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import RelayList from "@app/components/RelayList.svelte"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: string
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
relays: Readable<string[]>
|
||||||
|
addRelay: (url: string) => unknown
|
||||||
|
removeRelay: (url: string) => unknown
|
||||||
|
matchRelay?: (url: string) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const {icon, title, relays, subtitle, addRelay, removeRelay, matchRelay}: Props = $props()
|
||||||
|
|
||||||
|
const onclick = () =>
|
||||||
|
pushModal(RelayList, {title, subtitle, relays, addRelay, removeRelay, matchRelay})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn font-normal flex h-[unset] w-full flex-nowrap py-4 text-left items-start justify-between"
|
||||||
|
{onclick}>
|
||||||
|
<div class="flex flex-grow flex-row items-start gap-4">
|
||||||
|
<div class="flex h-7 w-7 flex-shrink-0 items-center justify-center">
|
||||||
|
<Icon {icon} />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<p class="text-lg">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end gap-1">
|
||||||
|
{#if $relays.length <= 1}
|
||||||
|
<Icon icon={DangerTriangle} />
|
||||||
|
{:else}
|
||||||
|
<Icon icon={Check} />
|
||||||
|
{/if}
|
||||||
|
{$relays.length}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
@@ -577,7 +577,7 @@ export const makeRoomId = (url: string, h: string) => `${url}'${h}`
|
|||||||
export const splitRoomId = (id: string) => id.split("'")
|
export const splitRoomId = (id: string) => id.split("'")
|
||||||
|
|
||||||
export const hasNip29 = (relay?: RelayProfile) =>
|
export const hasNip29 = (relay?: RelayProfile) =>
|
||||||
relay?.supported_nips?.map?.(String)?.includes?.("29")
|
Boolean(relay?.supported_nips?.map?.(String)?.includes?.("29"))
|
||||||
|
|
||||||
export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
|
export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
|
||||||
tracker,
|
tracker,
|
||||||
@@ -1224,3 +1224,8 @@ export const shouldNotify = (url: string, h?: string) => getShouldNotify(getSett
|
|||||||
|
|
||||||
export const deriveShouldNotify = (url: string, h?: string) =>
|
export const deriveShouldNotify = (url: string, h?: string) =>
|
||||||
derived(userSettingsValues, $settings => getShouldNotify($settings, url, h))
|
derived(userSettingsValues, $settings => getShouldNotify($settings, url, h))
|
||||||
|
|
||||||
|
// Whatever who cares
|
||||||
|
|
||||||
|
export const hasNip50 = (relay?: RelayProfile) =>
|
||||||
|
Boolean(relay?.supported_nips?.map?.(String)?.includes?.("50"))
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {slide} from "@lib/transition"
|
|
||||||
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
interface Props {
|
|
||||||
title?: import("svelte").Snippet
|
|
||||||
description?: import("svelte").Snippet
|
|
||||||
children?: import("svelte").Snippet
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const {...props}: Props = $props()
|
|
||||||
|
|
||||||
const toggle = () => {
|
|
||||||
isOpen = !isOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
let isOpen = $state(false)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="relative flex flex-col gap-4 {props.class}">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="absolute right-8 top-8 h-4 w-4 cursor-pointer transition-all"
|
|
||||||
class:rotate-90={!isOpen}
|
|
||||||
onclick={toggle}>
|
|
||||||
<Icon icon={AltArrowDown} />
|
|
||||||
</button>
|
|
||||||
{@render props.title?.()}
|
|
||||||
{@render props.description?.()}
|
|
||||||
{#if isOpen}
|
|
||||||
<div transition:slide>
|
|
||||||
{@render props.children?.()}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
import {sleep} from "@welshman/lib"
|
import {sleep} from "@welshman/lib"
|
||||||
import {Capacitor} from "@capacitor/core"
|
import {Capacitor} from "@capacitor/core"
|
||||||
import {Badge} from "@capawesome/capacitor-badge"
|
import {Badge} from "@capawesome/capacitor-badge"
|
||||||
|
import Bell from "@assets/icons/bell.svg?dataurl"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {Push, clearBadges} from "@app/util/notifications"
|
import {Push, clearBadges} from "@app/util/notifications"
|
||||||
@@ -51,7 +53,10 @@
|
|||||||
|
|
||||||
<form class="content column gap-4" {onsubmit}>
|
<form class="content column gap-4" {onsubmit}>
|
||||||
<div class="card2 bg-alt col-4 shadow-md">
|
<div class="card2 bg-alt col-4 shadow-md">
|
||||||
<strong class="text-lg">Alert Settings</strong>
|
<strong class="flex items-center gap-3 text-lg">
|
||||||
|
<Icon icon={Bell} />
|
||||||
|
Alert Settings
|
||||||
|
</strong>
|
||||||
{#await Badge.isSupported()}
|
{#await Badge.isSupported()}
|
||||||
<!-- pass -->
|
<!-- pass -->
|
||||||
{:then { isSupported }}
|
{:then { isSupported }}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Router} from "@welshman/router"
|
import {Router} from "@welshman/router"
|
||||||
import {userMuteList, tagPubkey, publishThunk, userBlossomServerList} from "@welshman/app"
|
import {userMuteList, tagPubkey, publishThunk, userBlossomServerList} from "@welshman/app"
|
||||||
|
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import InputList from "@lib/components/InputList.svelte"
|
import InputList from "@lib/components/InputList.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
@@ -49,7 +51,10 @@
|
|||||||
|
|
||||||
<form class="content column gap-4" {onsubmit}>
|
<form class="content column gap-4" {onsubmit}>
|
||||||
<div class="card2 bg-alt col-4 shadow-md">
|
<div class="card2 bg-alt col-4 shadow-md">
|
||||||
<strong class="text-lg">Content Settings</strong>
|
<strong class="flex items-center gap-3 text-lg">
|
||||||
|
<Icon icon={NotesMinimalistic} />
|
||||||
|
Content Settings
|
||||||
|
</strong>
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Hide sensitive content?</p>
|
<p>Hide sensitive content?</p>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import ShieldMinimalistic from "@assets/icons/shield-minimalistic.svg?dataurl"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PLATFORM_NAME, RelayAuthMode, userSettingsValues} from "@app/core/state"
|
import {PLATFORM_NAME, RelayAuthMode, userSettingsValues} from "@app/core/state"
|
||||||
@@ -24,7 +26,10 @@
|
|||||||
|
|
||||||
<form class="content column gap-4" {onsubmit}>
|
<form class="content column gap-4" {onsubmit}>
|
||||||
<div class="card2 bg-alt flex flex-col gap-4 shadow-md">
|
<div class="card2 bg-alt flex flex-col gap-4 shadow-md">
|
||||||
<strong class="text-lg">Privacy Settings</strong>
|
<strong class="flex items-center gap-3 text-lg">
|
||||||
|
<Icon icon={ShieldMinimalistic} />
|
||||||
|
Privacy Settings
|
||||||
|
</strong>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<p>Authenticate with unknown relays?</p>
|
<p>Authenticate with unknown relays?</p>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
|
import {derived} from "svelte/store"
|
||||||
|
import {shuffle, partition, ifLet} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
getRelayLists,
|
getRelayLists,
|
||||||
@@ -10,182 +12,182 @@
|
|||||||
removeBlockedRelay,
|
removeBlockedRelay,
|
||||||
addMessagingRelay,
|
addMessagingRelay,
|
||||||
removeMessagingRelay,
|
removeMessagingRelay,
|
||||||
|
addSearchRelay,
|
||||||
|
removeSearchRelay,
|
||||||
|
getRelay,
|
||||||
|
setWriteRelays,
|
||||||
|
setReadRelays,
|
||||||
|
setSearchRelays,
|
||||||
|
setMessagingRelays,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import {RelayMode} from "@welshman/util"
|
import {RelayMode} from "@welshman/util"
|
||||||
|
import Plane from "@assets/icons/plane.svg?dataurl"
|
||||||
|
import Inbox from "@assets/icons/inbox.svg?dataurl"
|
||||||
|
import Server from "@assets/icons/server.svg?dataurl"
|
||||||
|
import Mailbox from "@assets/icons/mailbox.svg?dataurl"
|
||||||
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
|
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
|
import ForbiddenCircle from "@assets/icons/forbidden-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Collapse from "@lib/components/Collapse.svelte"
|
import RelaySettingsItem from "@app/components/RelaySettingsItem.svelte"
|
||||||
import RelayItem from "@app/components/RelayItem.svelte"
|
import type {ActionItem} from "@app/components/RelaySettingsActionItem.svelte"
|
||||||
import RelayAdd from "@app/components/RelayAdd.svelte"
|
import RelaySettingsActionItems from "@app/components/RelaySettingsActionItems.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {hasNip50, DEFAULT_RELAYS, DEFAULT_MESSAGING_RELAYS} from "@app/core/state"
|
||||||
import {discoverRelays} from "@app/core/requests"
|
import {discoverRelays} from "@app/core/requests"
|
||||||
import Globus from "@assets/icons/globus.svg?dataurl"
|
|
||||||
import Inbox from "@assets/icons/inbox.svg?dataurl"
|
|
||||||
import Mailbox from "@assets/icons/mailbox.svg?dataurl"
|
|
||||||
import ForbiddenCircle from "@assets/icons/forbidden-circle.svg?dataurl"
|
|
||||||
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
|
||||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
|
||||||
|
|
||||||
const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read)
|
const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read)
|
||||||
const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write)
|
const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write)
|
||||||
|
const searchRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Search)
|
||||||
const blockedRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Blocked)
|
const blockedRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Blocked)
|
||||||
const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging)
|
const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging)
|
||||||
|
|
||||||
const addReadRelays = () =>
|
const addReadRelay = (url: string) => addRelay(url, RelayMode.Read)
|
||||||
pushModal(RelayAdd, {
|
|
||||||
relays: readRelayUrls,
|
|
||||||
addRelay: (url: string) => addRelay(url, RelayMode.Read),
|
|
||||||
})
|
|
||||||
|
|
||||||
const addWriteRelays = () =>
|
|
||||||
pushModal(RelayAdd, {
|
|
||||||
relays: writeRelayUrls,
|
|
||||||
addRelay: (url: string) => addRelay(url, RelayMode.Write),
|
|
||||||
})
|
|
||||||
|
|
||||||
const addBlockedRelays = () =>
|
|
||||||
pushModal(RelayAdd, {relays: blockedRelayUrls, addRelay: addBlockedRelay})
|
|
||||||
|
|
||||||
const addMessagingRelays = () =>
|
|
||||||
pushModal(RelayAdd, {relays: messagingRelayUrls, addRelay: addMessagingRelay})
|
|
||||||
|
|
||||||
const removeReadRelay = (url: string) => removeRelay(url, RelayMode.Read)
|
const removeReadRelay = (url: string) => removeRelay(url, RelayMode.Read)
|
||||||
|
const addWriteRelay = (url: string) => addRelay(url, RelayMode.Write)
|
||||||
const removeWriteRelay = (url: string) => removeRelay(url, RelayMode.Write)
|
const removeWriteRelay = (url: string) => removeRelay(url, RelayMode.Write)
|
||||||
|
const showActionItems = () => pushModal(RelaySettingsActionItems, {actionItems})
|
||||||
|
|
||||||
|
const actionItems = derived(
|
||||||
|
[readRelayUrls, writeRelayUrls, messagingRelayUrls, searchRelayUrls],
|
||||||
|
([$readRelayUrls, $writeRelayUrls, $messagingRelayUrls, $searchRelayUrls]) => {
|
||||||
|
const $actionItems: ActionItem[] = []
|
||||||
|
|
||||||
|
if ($readRelayUrls.length <= 1) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Missing Inbox Relays",
|
||||||
|
subtitle: "Other people aren't currently able to reliably tag you in public notes.",
|
||||||
|
action: "Update",
|
||||||
|
apply: () => setReadRelays(DEFAULT_RELAYS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($writeRelayUrls.length <= 1) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Missing Outbox Relays",
|
||||||
|
subtitle: "Other people aren't currently able to reliably find your public notes.",
|
||||||
|
action: "Update",
|
||||||
|
apply: () => setWriteRelays(DEFAULT_RELAYS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($messagingRelayUrls.length <= 1) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Missing DM Relays",
|
||||||
|
subtitle: "You aren't currently able to reliably send or receive direct messages.",
|
||||||
|
action: "Update",
|
||||||
|
apply: () => setMessagingRelays(DEFAULT_MESSAGING_RELAYS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($readRelayUrls.length > 8) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Too Many Inbox Relays",
|
||||||
|
subtitle:
|
||||||
|
"You have more inbox relays than is really necessary, which can affect resource usage.",
|
||||||
|
action: "Prune Selections",
|
||||||
|
apply: () => setReadRelays(shuffle($readRelayUrls).slice(0, 5)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($writeRelayUrls.length > 8) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Too Many Outbox Relays",
|
||||||
|
subtitle:
|
||||||
|
"You have more outbox relays than is really necessary, which can affect resource usage.",
|
||||||
|
action: "Prune Selections",
|
||||||
|
apply: () => setWriteRelays(shuffle($writeRelayUrls).slice(0, 5)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($messagingRelayUrls.length > 8) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Too Many DM Relays",
|
||||||
|
subtitle:
|
||||||
|
"You have more DM relays than is really necessary, which can affect resource usage.",
|
||||||
|
action: "Prune Selections",
|
||||||
|
apply: () => setMessagingRelays(shuffle($messagingRelayUrls).slice(0, 5)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const [okSearchRelays, badSearchRelays] = partition(
|
||||||
|
url => Boolean(ifLet(getRelay(url), hasNip50)),
|
||||||
|
$searchRelayUrls,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (badSearchRelays.length > 0) {
|
||||||
|
$actionItems.push({
|
||||||
|
title: "Invalid Search Relays",
|
||||||
|
subtitle: `Some of your search relays don't support search.`,
|
||||||
|
action: "Remove Invalid",
|
||||||
|
apply: () => setSearchRelays(okSearchRelays),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return $actionItems
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
discoverRelays(getRelayLists())
|
discoverRelays(getRelayLists())
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content column gap-4">
|
<div class="content">
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
<div class="card2 bg-alt flex flex-col gap-4 shadow-md">
|
||||||
{#snippet title()}
|
<div class="flex justify-between">
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
<strong class="flex items-center gap-3 text-lg">
|
||||||
<Icon icon={Globus} />
|
<Icon icon={Server} />
|
||||||
Outbox Relays
|
Your Relays
|
||||||
</h2>
|
</strong>
|
||||||
{/snippet}
|
{#if $actionItems.length > 0}
|
||||||
{#snippet description()}
|
<Button class="btn btn-neutral btn-sm" onclick={showActionItems}>
|
||||||
<p class="text-sm">
|
<Icon icon={DangerTriangle} />
|
||||||
These relays will be advertised on your profile as places where you send your public notes.
|
{$actionItems.length} Issue{$actionItems.length === 1 ? "" : "s"} Detected
|
||||||
Be sure to select relays that will accept your notes, and which will let people who follow
|
</Button>
|
||||||
you read them.
|
{/if}
|
||||||
</p>
|
|
||||||
{/snippet}
|
|
||||||
<div class="column gap-2">
|
|
||||||
{#each $writeRelayUrls.sort() as url (url)}
|
|
||||||
<RelayItem {url}>
|
|
||||||
<Button
|
|
||||||
class="tooltip flex items-center"
|
|
||||||
data-tip="Stop using this relay"
|
|
||||||
onclick={() => removeWriteRelay(url)}>
|
|
||||||
<Icon icon={CloseCircle} />
|
|
||||||
</Button>
|
|
||||||
</RelayItem>
|
|
||||||
{:else}
|
|
||||||
<p class="text-center text-sm">No relays found</p>
|
|
||||||
{/each}
|
|
||||||
<Button class="btn btn-primary mt-2" onclick={addWriteRelays}>
|
|
||||||
<Icon icon={AddCircle} />
|
|
||||||
Add Relay
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
<p class="text-sm mb-2">
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
Relays are servers which store your data, or allow you to find data from across the Nostr
|
||||||
{#snippet title()}
|
network. We've set you up with some reasonable defaults, but if you're a power user, you can
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
customize your relay selections below.
|
||||||
<Icon icon={Inbox} />
|
</p>
|
||||||
Inbox Relays
|
<RelaySettingsItem
|
||||||
</h2>
|
icon={Inbox}
|
||||||
{/snippet}
|
title="Inbox Relays"
|
||||||
{#snippet description()}
|
subtitle="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 class="text-sm">
|
relays={readRelayUrls}
|
||||||
These relays will be advertised on your profile as places where other people should send
|
addRelay={addReadRelay}
|
||||||
notes intended for you. Be sure to select relays that will accept notes that tag you.
|
removeRelay={removeReadRelay} />
|
||||||
</p>
|
<RelaySettingsItem
|
||||||
{/snippet}
|
icon={Plane}
|
||||||
<div class="column gap-2">
|
title="Outbox Relays"
|
||||||
{#each $readRelayUrls.sort() as url (url)}
|
subtitle="Where other people should send notes intended for you. Be sure to select relays that will accept notes that tag you."
|
||||||
<RelayItem {url}>
|
relays={writeRelayUrls}
|
||||||
<Button
|
addRelay={addWriteRelay}
|
||||||
class="tooltip flex items-center"
|
removeRelay={removeWriteRelay} />
|
||||||
data-tip="Stop using this relay"
|
<RelaySettingsItem
|
||||||
onclick={() => removeReadRelay(url)}>
|
icon={Mailbox}
|
||||||
<Icon icon={CloseCircle} />
|
title="DM Relays"
|
||||||
</Button>
|
subtitle="Where you 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."
|
||||||
</RelayItem>
|
relays={messagingRelayUrls}
|
||||||
{:else}
|
addRelay={addMessagingRelay}
|
||||||
<p class="text-center text-sm">No relays found</p>
|
removeRelay={removeMessagingRelay} />
|
||||||
{/each}
|
<RelaySettingsItem
|
||||||
<Button class="btn btn-primary mt-2" onclick={addReadRelays}>
|
icon={Magnifier}
|
||||||
<Icon icon={AddCircle} />
|
title="Search Relays"
|
||||||
Add Relay
|
subtitle="Relays that support searching for profiles and public notes."
|
||||||
</Button>
|
relays={searchRelayUrls}
|
||||||
</div>
|
addRelay={addSearchRelay}
|
||||||
</Collapse>
|
removeRelay={removeSearchRelay}
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
matchRelay={url => hasNip50(getRelay(url))} />
|
||||||
{#snippet title()}
|
<RelaySettingsItem
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
icon={ForbiddenCircle}
|
||||||
<Icon icon={Mailbox} />
|
title="Blocked Relays"
|
||||||
Messaging Relays
|
subtitle="These relays won't be used unless explicitly requested."
|
||||||
</h2>
|
relays={blockedRelayUrls}
|
||||||
{/snippet}
|
addRelay={addBlockedRelay}
|
||||||
{#snippet description()}
|
removeRelay={removeBlockedRelay} />
|
||||||
<p class="text-sm">
|
</div>
|
||||||
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>
|
|
||||||
{/snippet}
|
|
||||||
<div class="column gap-2">
|
|
||||||
{#each $messagingRelayUrls.sort() as url (url)}
|
|
||||||
<RelayItem {url}>
|
|
||||||
<Button
|
|
||||||
class="tooltip flex items-center"
|
|
||||||
data-tip="Stop using this relay"
|
|
||||||
onclick={() => removeMessagingRelay(url)}>
|
|
||||||
<Icon icon={CloseCircle} />
|
|
||||||
</Button>
|
|
||||||
</RelayItem>
|
|
||||||
{:else}
|
|
||||||
<p class="text-center text-sm">No relays found</p>
|
|
||||||
{/each}
|
|
||||||
<Button class="btn btn-primary mt-2" onclick={addMessagingRelays}>
|
|
||||||
<Icon icon={AddCircle} />
|
|
||||||
Add Relay
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
<Collapse class="card2 bg-alt column gap-4 shadow-md">
|
|
||||||
{#snippet title()}
|
|
||||||
<h2 class="flex items-center gap-3 text-xl">
|
|
||||||
<Icon icon={ForbiddenCircle} />
|
|
||||||
Blocked Relays
|
|
||||||
</h2>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet description()}
|
|
||||||
<p class="text-sm">
|
|
||||||
These relays will never be connected to by clients supporting this policy.
|
|
||||||
</p>
|
|
||||||
{/snippet}
|
|
||||||
<div class="column gap-2">
|
|
||||||
{#each $blockedRelayUrls.sort() as url (url)}
|
|
||||||
<RelayItem {url}>
|
|
||||||
<Button
|
|
||||||
class="tooltip flex items-center"
|
|
||||||
data-tip="Stop using this relay"
|
|
||||||
onclick={() => removeBlockedRelay(url)}>
|
|
||||||
<Icon icon={CloseCircle} />
|
|
||||||
</Button>
|
|
||||||
</RelayItem>
|
|
||||||
{:else}
|
|
||||||
<p class="text-center text-sm">No relays found</p>
|
|
||||||
{/each}
|
|
||||||
<Button class="btn btn-primary mt-2" onclick={addBlockedRelays}>
|
|
||||||
<Icon icon={AddCircle} />
|
|
||||||
Add Relay
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,9 +83,9 @@
|
|||||||
<div class="content column gap-4">
|
<div class="content column gap-4">
|
||||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-md">
|
<div class="card2 bg-alt flex flex-col gap-6 shadow-md">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-3">
|
<strong class="flex items-center gap-3 text-lg">
|
||||||
<Icon icon={Wallet2} />
|
<Icon icon={Wallet2} />
|
||||||
Wallet
|
Your Wallet
|
||||||
</strong>
|
</strong>
|
||||||
{#if $session?.wallet}
|
{#if $session?.wallet}
|
||||||
<div class={statusClass}>
|
<div class={statusClass}>
|
||||||
|
|||||||
Reference in New Issue
Block a user