mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-28 16:00:52 +00:00
Re-design relays page and person relays list with metadata
This commit is contained in:
parent
f44515d0df
commit
a8efa9ffc2
@ -25,8 +25,10 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
"a11y-click-events-have-key-events": "off",
|
||||
"a11y-autofocus": "off",
|
||||
"no-unused-vars": ["error", {args: "none"}],
|
||||
"no-async-promise-executor": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
ignorePatterns: ["*.svg"]
|
||||
ignorePatterns: ["*.svg"],
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run qa:lint
|
||||
npm run qa:check
|
||||
npm run check
|
||||
|
||||
|
21
README.md
21
README.md
@ -34,9 +34,12 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
# Missions
|
||||
|
||||
- [ ] Image uploads
|
||||
- Use dufflepud. Default will charge via lightning and have a tos, others can self-host and skip that.
|
||||
- Default will charge via lightning and have a tos, others can self-host and skip that.
|
||||
- Add banner field to profile
|
||||
- [ ] Allow users to select which relay they're reading from, allow more advanced custom feeds ala tweetdeck
|
||||
- Linode/Digital Ocean
|
||||
- https://github.com/brandonsavage/Upload
|
||||
- https://github.com/seaweedfs/seaweedfs
|
||||
- https://github.com/cubefs/cubefs
|
||||
- [ ] Add relay selector when publishing a note
|
||||
- [ ] Support invoices, tips, zaps https://twitter.com/jb55/status/1604131336247476224
|
||||
- [ ] Separate settings for read, write, and broadcast relays based on NIP 65
|
||||
@ -53,9 +56,21 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- https://github.com/nostr-protocol/nips/pull/205#issuecomment-1419234230
|
||||
- [ ] Change network tab to list relays the user is connected to
|
||||
- [ ] Sync mentions box and in-reply mentions
|
||||
- [ ] Add petnames for channels
|
||||
- [ ] Channels
|
||||
- [ ] Ability to leave/mute DM conversation
|
||||
- [ ] Add petnames for channels
|
||||
- [ ] Add notifications for chat messages
|
||||
|
||||
# Current
|
||||
|
||||
- [x] Re-design relays page and person relays list with metadata
|
||||
- [ ] Add relay selection to new note screen
|
||||
- [ ] Add relay selection to reply widget
|
||||
- [ ] Make feeds page customizable. This could potentially use the "lists" NIP
|
||||
- [ ] Show notification at top of feeds: "Showing notes from 3 relays". Click to customize.
|
||||
- [ ] Click through on relays page to view a feed for only that relay.
|
||||
- [ ] Custom views: slider between fast/complete with a warning at either extreme
|
||||
|
||||
# Changelog
|
||||
|
||||
## 0.2.10
|
||||
|
@ -7,9 +7,9 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"qa:lint": "eslint src/*/** --quiet",
|
||||
"qa:check": "svelte-check --tsconfig ./tsconfig.json --threshold error",
|
||||
"qa:all": "run-p qa:lint qa:check",
|
||||
"check:es": "eslint src/*/** --quiet",
|
||||
"check:ts": "svelte-check --tsconfig ./tsconfig.json --threshold error",
|
||||
"check": "run-p check:*",
|
||||
"watch": "find src -type f | entr -r"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -10,8 +10,8 @@
|
||||
import {Router, Route, links, navigate} from "svelte-routing"
|
||||
import {globalHistory} from "svelte-routing/src/history"
|
||||
import {displayPerson, isLike} from 'src/util/nostr'
|
||||
import {timedelta, now, sleep} from 'src/util/misc'
|
||||
import {keys, user, pool, getRelays} from 'src/agent'
|
||||
import {timedelta, shuffle, now, sleep} from 'src/util/misc'
|
||||
import {db, keys, user, pool, getRelays} from 'src/agent'
|
||||
import {modal, toast, settings, logUsage, alerts, messages, loadAppData} from "src/app"
|
||||
import {routes} from "src/app/ui"
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
@ -82,7 +82,14 @@
|
||||
loadAppData($user.pubkey)
|
||||
}
|
||||
|
||||
// Background work
|
||||
|
||||
const interval = setInterval(() => {
|
||||
alertSlowConnections()
|
||||
retrieveRelayMeta()
|
||||
}, 2_000)
|
||||
|
||||
const alertSlowConnections = () => {
|
||||
// Only notify about relays the user is actually subscribed to
|
||||
const relayUrls = pluck('url', getRelays())
|
||||
|
||||
@ -101,7 +108,34 @@
|
||||
// Alert the user to any heinously slow connections
|
||||
slowConnections = pool.getConnections()
|
||||
.filter(({url, stats: s}) => relayUrls.includes(url) && s.timer / s.count > 3000)
|
||||
}, 10_000)
|
||||
}
|
||||
|
||||
const retrieveRelayMeta = async () => {
|
||||
const {dufflepudUrl} = $settings
|
||||
|
||||
// Find relays with old/missing metadata and refresh them. Only pick a
|
||||
// few so we're not sending too many concurrent http requests
|
||||
const allRelays = await db.table('relays').toArray()
|
||||
const staleRelays = allRelays
|
||||
.filter(r => (r.refreshed_at || 0) < now() - timedelta(7, 'days'))
|
||||
const staleRelaysSample = shuffle(staleRelays).slice(0, 10)
|
||||
|
||||
const freshRelays = await Promise.all(
|
||||
staleRelaysSample.map(async ({url}) => {
|
||||
const res = await fetch(dufflepudUrl + '/relay/info', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
return {...await res.json(), url, refreshed_at: now()}
|
||||
})
|
||||
)
|
||||
|
||||
db.table('relays').bulkPut(freshRelays)
|
||||
}
|
||||
|
||||
// Close menu on click outside
|
||||
document.querySelector("html").addEventListener("click", e => {
|
||||
|
@ -23,7 +23,7 @@ export const loadAppData = pubkey => {
|
||||
])
|
||||
}
|
||||
|
||||
export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}, usingExtension = false) => {
|
||||
export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}) => {
|
||||
if (privkey) {
|
||||
keys.setPrivateKey(privkey)
|
||||
} else {
|
||||
|
87
src/partials/RelayCard.svelte
Normal file
87
src/partials/RelayCard.svelte
Normal file
@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import {last, find, propEq} from 'ramda'
|
||||
import {onMount} from 'svelte'
|
||||
import {poll} from "src/util/misc"
|
||||
import {switcher} from 'hurdak/lib/hurdak'
|
||||
import {fly} from 'svelte/transition'
|
||||
import Toggle from "src/partials/Toggle.svelte"
|
||||
import {user, pool} from "src/agent"
|
||||
import {addRelay, removeRelay, setRelayWriteCondition} from "src/app"
|
||||
|
||||
export let relay
|
||||
export let i = 0
|
||||
export let showControls = false
|
||||
|
||||
let status = null
|
||||
let showStatus = false
|
||||
let joined = false
|
||||
|
||||
$: joined = find(propEq('url', relay.url), $user?.relays || [])
|
||||
|
||||
onMount(() => {
|
||||
return poll(300, async () => {
|
||||
const conn = find(propEq('url', relay.url), pool.getConnections())
|
||||
|
||||
if (conn) {
|
||||
const slow = conn.status === 'ready' && conn.stats.timer / conn.stats.count > 1000
|
||||
|
||||
// Be more strict here than with alerts
|
||||
status = slow ? 'slow' : conn.status
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="rounded border border-solid border-medium bg-dark shadow flex flex-col justify-between gap-3 py-3 px-6"
|
||||
in:fly={{y: 20, delay: i * 100}}>
|
||||
<div class="flex gap-2 items-center justify-between">
|
||||
<div class="flex gap-2 items-center text-xl">
|
||||
<i class={relay.url.startsWith('wss') ? "fa fa-lock" : "fa fa-unlock"} />
|
||||
<span>{last(relay.url.split('://'))}</span>
|
||||
{#if joined}
|
||||
<span
|
||||
on:mouseout={() => {showStatus = false}}
|
||||
on:mouseover={() => {showStatus = true}}
|
||||
class="w-2 h-2 rounded-full bg-medium cursor-pointer"
|
||||
class:bg-danger={status === 'error'}
|
||||
class:bg-warning={['pending', 'slow'].includes(status)}
|
||||
class:bg-success={status === 'ready'}>
|
||||
</span>
|
||||
<p
|
||||
class="text-light text-sm transition-all"
|
||||
class:opacity-0={!showStatus}
|
||||
class:opacity-1={showStatus}>
|
||||
{switcher(status, {
|
||||
error: 'Not connected',
|
||||
pending: 'Trying to connect',
|
||||
slow: 'Slow connection',
|
||||
ready: 'Connected',
|
||||
default: 'Waiting to reconnect',
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if joined}
|
||||
<button class="flex gap-3 items-center text-light" on:click={() => removeRelay(relay.url)}>
|
||||
<i class="fa fa-right-from-bracket" /> Leave
|
||||
</button>
|
||||
{:else}
|
||||
<button class="flex gap-3 items-center text-light" on:click={() => addRelay(relay.url)}>
|
||||
<i class="fa fa-right-to-bracket" /> Join
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if relay.description}
|
||||
<p>{relay.description}</p>
|
||||
{/if}
|
||||
{#if joined && showControls}
|
||||
<div class="border-b border-solid border-medium -mx-6" />
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>Publish to this relay?</span>
|
||||
<Toggle
|
||||
value={relay.write !== "!"}
|
||||
on:change={() => setRelayWriteCondition(relay.url, relay.write === "!" ? "" : "!")} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -11,7 +11,7 @@
|
||||
const {nostr} = window as any
|
||||
|
||||
if (nostr) {
|
||||
await login({pubkey: await nostr.getPublicKey()}, true)
|
||||
await login({pubkey: await nostr.getPublicKey()})
|
||||
} else {
|
||||
modal.set({type: 'login/privkey'})
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
<script>
|
||||
import {liveQuery} from 'dexie'
|
||||
import {whereEq, find, last} from 'ramda'
|
||||
import {noop} from 'hurdak/lib/hurdak'
|
||||
import {pluck} from 'ramda'
|
||||
import {noop, createMap} from 'hurdak/lib/hurdak'
|
||||
import {onMount} from 'svelte'
|
||||
import {get} from 'svelte/store'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fuzzy, poll} from "src/util/misc"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Toggle from "src/partials/Toggle.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import RelayCard from "src/partials/RelayCard.svelte"
|
||||
import {pool, db, user, ready} from "src/agent"
|
||||
import {modal, addRelay, removeRelay, setRelayWriteCondition, settings} from "src/app"
|
||||
import {modal, settings} from "src/app"
|
||||
import defaults from "src/agent/defaults"
|
||||
|
||||
let q = ""
|
||||
@ -21,9 +21,9 @@
|
||||
|
||||
fetch(get(settings).dufflepudUrl + '/relay')
|
||||
.then(async res => {
|
||||
const {relays} = await res.json()
|
||||
const json = await res.json()
|
||||
|
||||
for (const url of relays) {
|
||||
for (const url of json.relays) {
|
||||
db.table('relays').put({url})
|
||||
}
|
||||
}).catch(noop)
|
||||
@ -34,23 +34,27 @@
|
||||
|
||||
const knownRelays = liveQuery(() => db.table('relays').toArray())
|
||||
|
||||
$: search = fuzzy($knownRelays, {keys: ["name", "description", "url"]})
|
||||
$: {
|
||||
const joined = pluck('url', $user?.relays || [])
|
||||
const data = ($knownRelays || []).filter(r => !joined.includes(r.url))
|
||||
|
||||
const join = async url => {
|
||||
await addRelay({url})
|
||||
}
|
||||
|
||||
const leave = async url => {
|
||||
await removeRelay(url)
|
||||
search = fuzzy(data, {keys: ["name", "description", "url"]})
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Attempt to connect so we can show status
|
||||
relays.forEach(relay => pool.connect(relay.url))
|
||||
|
||||
return poll(300, () => {
|
||||
return poll(300, async () => {
|
||||
if ($ready) {
|
||||
relays = $user?.relays || []
|
||||
const userRelays = $user?.relays || []
|
||||
const urls = pluck('url', userRelays)
|
||||
const relaysByUrl = createMap(
|
||||
'url',
|
||||
await db.table('relays').where('url').anyOf(urls).toArray()
|
||||
)
|
||||
|
||||
relays = userRelays.map(relay => ({...relaysByUrl[relay.url], ...relay}))
|
||||
}
|
||||
|
||||
status = Object.fromEntries(
|
||||
@ -84,49 +88,9 @@
|
||||
{#if relays.length === 0}
|
||||
<div class="text-center">No relays connected</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{#each relays as {url, write}, i (url)}
|
||||
<div class="rounded border border-solid border-medium bg-dark shadow" in:fly={{y: 20, delay: i * 100}}>
|
||||
<div class="flex flex-col gap-2 py-3 px-6">
|
||||
<div class="flex gap-2 items-center justify-between">
|
||||
<strong class="flex gap-2 items-center">
|
||||
<i class={url.startsWith('wss') ? "fa fa-lock" : "fa fa-unlock"} />
|
||||
{last(url.split('://'))}
|
||||
</strong>
|
||||
<button class="fa fa-times cursor-pointer" on:click={() => leave(url)} />
|
||||
</div>
|
||||
<p class="text-light">
|
||||
{#if status[url] === 'error'}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="inline-block w-2 h-2 rounded bg-danger" /> Not connected
|
||||
</div>
|
||||
{:else if status[url] === 'pending'}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="inline-block w-2 h-2 rounded bg-warning" /> Trying to connect
|
||||
</div>
|
||||
{:else if status[url] === 'slow'}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="inline-block w-2 h-2 rounded bg-warning" /> Slow connection
|
||||
</div>
|
||||
{:else if status[url] === 'ready'}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="inline-block w-2 h-2 rounded bg-success" /> Connected
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="inline-block w-2 h-2 rounded bg-medium" /> Waiting to reconnect
|
||||
</div>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-b border-solid border-medium" />
|
||||
<div class="flex justify-between gap-2 py-3 px-6">
|
||||
<span>Publish to this relay?</span>
|
||||
<Toggle
|
||||
value={write !== "!"}
|
||||
on:change={() => setRelayWriteCondition(url, write === "!" ? "" : "!")} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{#each relays as relay, i (relay.url)}
|
||||
<RelayCard showControls {relay} {i} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex flex-col gap-6" in:fly={{y: 20, delay: 1000}}>
|
||||
@ -144,18 +108,8 @@
|
||||
<i slot="before" class="fa-solid fa-search" />
|
||||
</Input>
|
||||
{/if}
|
||||
{#each (search(q) || []).slice(0, 50) as {url, name, description} (url)}
|
||||
{#if !find(whereEq({url}), relays)}
|
||||
<div class="flex gap-2 justify-between">
|
||||
<div>
|
||||
<strong>{name || url}</strong>
|
||||
<p class="text-light">{description || ''}</p>
|
||||
</div>
|
||||
<button class="underline cursor-pointer" on:click={() => join(url)}>
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each (search(q) || []).slice(0, 50) as relay, i (relay.url)}
|
||||
<RelayCard {relay} {i} />
|
||||
{/each}
|
||||
<small class="text-center">
|
||||
Showing {Math.min(($knownRelays || []).length - relays.length, 50)}
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
let input = null
|
||||
|
||||
const onSubmit = async e => {
|
||||
const onSubmit = async () => {
|
||||
const {content, mentions, topics} = input.parse()
|
||||
|
||||
if (content) {
|
||||
|
@ -1,10 +1,7 @@
|
||||
<script>
|
||||
import {last, find, whereEq} from 'ramda'
|
||||
import {fly} from 'svelte/transition'
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import {user} from 'src/agent'
|
||||
import {addRelay, removeRelay} from "src/app"
|
||||
import RelayCard from "src/partials/RelayCard.svelte"
|
||||
|
||||
export let person
|
||||
</script>
|
||||
@ -18,20 +15,10 @@
|
||||
{#if (person.relays || []).length === 0}
|
||||
<div class="pt-8 text-center">No relays found</div>
|
||||
{:else}
|
||||
{#each person.relays as {url, write}, i (url)}
|
||||
<div class="rounded border border-solid border-medium bg-dark shadow p-2 px-3">
|
||||
<div class="flex gap-2 items-center justify-between">
|
||||
<strong class="flex gap-2 items-center">
|
||||
<i class={url.startsWith('wss') ? "fa fa-lock" : "fa fa-unlock"} />
|
||||
{last(url.split('://'))}
|
||||
</strong>
|
||||
{#if find(whereEq({url}), $user.relays)}
|
||||
<Anchor type="button" on:click={() => removeRelay({url})}>Leave</Anchor>
|
||||
{:else}
|
||||
<Anchor type="button" on:click={() => addRelay({url})}>Join</Anchor>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#each person.relays as relay, i (relay.url)}
|
||||
{#if relay.write !== '!'}
|
||||
<RelayCard {relay} {i} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</Content>
|
||||
|
Loading…
Reference in New Issue
Block a user