Add some state management stuff

This commit is contained in:
Jon Staab
2024-08-06 15:46:37 -07:00
parent 36a920df51
commit fb04a68168
18 changed files with 5474 additions and 5206 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
package-lock.json -diff

141
package-lock.json generated
View File

@@ -10,6 +10,9 @@
"dependencies": {
"@poppanator/sveltekit-svg": "^4.2.1",
"@welshman/lib": "^0.0.12",
"@welshman/net": "^0.0.16",
"@welshman/store": "^0.0.1",
"@welshman/util": "^0.0.23",
"daisyui": "^4.12.10",
"nostr-login": "^1.5.2",
"prettier-plugin-tailwindcss": "^0.6.5"
@@ -1402,6 +1405,110 @@
"throttle-debounce": "^5.0.0"
}
},
"node_modules/@welshman/net": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@welshman/net/-/net-0.0.16.tgz",
"integrity": "sha512-9NoNKs3BxMs7biyfhLtQFs0rJP5e8raMMjCXYL0+WaHTSPi1VaKBzXGlsikF713pJ0xm5kigAxa6wquonsWHtg==",
"dependencies": {
"@welshman/lib": "0.0.12",
"@welshman/util": "0.0.23",
"isomorphic-ws": "^5.0.0",
"ws": "^8.16.0"
}
},
"node_modules/@welshman/store": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@welshman/store/-/store-0.0.1.tgz",
"integrity": "sha512-vpnbJOF8zoneTcLOr5iZuRETVwb67mUyLiWmGsfs8yoMI7lpxJkg0QKieVkp2FpVYoDYbb98eQEuYOiHdsf7RQ==",
"dependencies": {
"svelte": "^4.2.18"
}
},
"node_modules/@welshman/util": {
"version": "0.0.23",
"resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.23.tgz",
"integrity": "sha512-xV9RnPvO3XZ163TD/5ga/vCnLtejMpkIu9JlfiO/iyHJ1DdObq9WkyuHTgVDNxkrPBf74KuoF9gnub9rLMCj/w==",
"dependencies": {
"@welshman/lib": "0.0.12",
"nostr-tools": "^2.3.2"
}
},
"node_modules/@welshman/util/node_modules/@noble/ciphers": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@welshman/util/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@welshman/util/node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@welshman/util/node_modules/@noble/hashes": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@welshman/util/node_modules/@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/@welshman/util/node_modules/nostr-tools": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.1.tgz",
"integrity": "sha512-4qAvlHSqBAA8lQMwRWE6dalSNdQT77Xut9lPiJZgEcb9RAlR69wR2+KVBAgnZVaabVYH7FJ7gOQXLw/jQBAYBg==",
"dependencies": {
"@noble/ciphers": "^0.5.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.1",
"@scure/base": "1.1.1",
"@scure/bip32": "1.3.1",
"@scure/bip39": "1.2.1"
},
"optionalDependencies": {
"nostr-wasm": "v0.1.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -2946,6 +3053,14 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/isomorphic-ws": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
"peerDependencies": {
"ws": "*"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@@ -3384,6 +3499,12 @@
}
]
},
"node_modules/nostr-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
"optional": true
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -5137,6 +5258,26 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",

View File

@@ -36,6 +36,9 @@
"dependencies": {
"@poppanator/sveltekit-svg": "^4.2.1",
"@welshman/lib": "^0.0.12",
"@welshman/net": "^0.0.16",
"@welshman/store": "^0.0.1",
"@welshman/util": "^0.0.23",
"daisyui": "^4.12.10",
"nostr-login": "^1.5.2",
"prettier-plugin-tailwindcss": "^0.6.5"

View File

@@ -69,18 +69,22 @@
--stark-content: #111;
}
.text-stark {
.text-stark,
.hover\:text-stark:hover {
color: var(--stark);
}
.text-stark-content {
.text-stark-content,
.hover\:text-stark-content:hover {
color: var(--stark-content);
}
.bg-stark {
.bg-stark,
.hover\:bg-stark:hover {
background-color: var(--stark);
}
.bg-stark-content {
.bg-stark-content,
.hover\:bg-stark-content:hover {
background-color: var(--stark-content);
}

38
src/app/base.ts Normal file
View File

@@ -0,0 +1,38 @@
import type {SignedEvent} from "@welshman/util"
import {Repository, Relay} from "@welshman/util"
import {NetworkContext, Tracker} from "@welshman/net"
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
export const repository = new Repository()
export const relay = new Relay(repository)
export const tracker = new Tracker()
const seenChallenges = new Set()
Object.assign(NetworkContext, {
onEvent: (url: string, event: SignedEvent) => tracker.track(event.id, url),
isDeleted: (url: string, event: SignedEvent) => repository.isDeleted(event),
// onAuth: async (url, challenge) => {
// if (seenChallenges.has(challenge)) {
// return
// }
// seenChallenges.add(challenge)
// const event = await signer.get().signAsUser(
// createEvent(22242, {
// tags: [
// ["relay", url],
// ["challenge", challenge],
// ],
// }),
// )
// NetworkContext.pool.get(url).send(["AUTH", event])
// return event
// },
})

15
src/app/commands.ts Normal file
View File

@@ -0,0 +1,15 @@
import {batch, postJson} from "@welshman/lib"
import {normalizeRelayUrl} from "@welshman/util"
import {relayInfo} from "app/state"
export const loadRelay = batch(1000, async (urls: string[]) => {
const data = await postJson(`${DUFFLEPUD_URL}/relay/info`, {urls})
relayInfo.update($relayInfo => {
for (const {url, info} of data) {
$relayInfo.set(normalizeRelayUrl(url), info)
}
return $relayInfo
})
})

View File

@@ -8,7 +8,8 @@
<script lang="ts">
import Icon from "@lib/components/Icon.svelte"
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
import {spaces} from "@app/state"
import {getGroupName, getGroupPicture, makeGroupId} from "@app/domain"
import {userGroupRelaysByNom, groupsById} from "@app/state"
</script>
<div class="relative w-14 bg-base-100">
@@ -22,10 +23,12 @@
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
</div>
</PrimaryNavItem>
{#each $spaces as { id, name, picture } (id)}
{#each $userGroupRelaysByNom.entries() as [nom, relays] (nom)}
{@const event = $groupsById.get(makeGroupId(relays[0], nom))}
{@const name = getGroupName(event)}
<PrimaryNavItem title={name}>
<div class="w-10 rounded-full border border-solid border-base-300">
<img alt={name} src={picture} />
<img alt={name} src={getGroupPicture(event)} />
</div>
</PrimaryNavItem>
{/each}

View File

@@ -4,7 +4,7 @@
</script>
<div class="flex w-60 flex-col gap-1 bg-base-300 px-2 py-4">
<SecondaryNavItem href="/">
<SecondaryNavItem href="/home">
<Icon icon="home-smile" /> Home
</SecondaryNavItem>
<SecondaryNavItem href="/people">

22
src/app/domain.ts Normal file
View File

@@ -0,0 +1,22 @@
import {stripProtocol} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {getIdentifier, normalizeRelayUrl} from "@welshman/util"
export const GROUP_DELIMITER = `'`
export const makeGroupId = (url: string, nom: string) =>
[stripProtocol(url), nom].join(GROUP_DELIMITER)
export const getGroupNom = (e: TrustedEvent) => getIdentifier(e)?.split(GROUP_DELIMITER)[1]
export const getGroupUrl = (e: TrustedEvent) => {
const id = getIdentifier(e)
const url = id?.split(GROUP_DELIMITER)[0]
return url ? normalizeRelayUrl(url) : null
}
export const getGroupName = (e: TrustedEvent | undefined) => e?.tags.find(t => t[0] === "name")?.[1]
export const getGroupPicture = (e: TrustedEvent | undefined) =>
e?.tags.find(t => t[0] === "picture")?.[1]

View File

@@ -1,5 +1,4 @@
import type {ComponentType} from "svelte"
import {readable, writable} from "svelte/store"
import {randomId} from "@welshman/lib"
import {pushState} from "$app/navigation"

View File

@@ -1,10 +1,44 @@
import {readable} from "svelte/store"
import {writable, derived} from "svelte/store"
import {pushToMapKey, indexBy} from "@welshman/lib"
import {getIdentifier, GROUP_META, GROUPS, getGroupTagValues} from "@welshman/util"
import {deriveEvents} from "@welshman/store"
import {repository} from "@app/base"
import {getGroupUrl, GROUP_DELIMITER} from "@app/domain"
export const spaces = readable([
{
id: "test",
name: "Test",
picture:
"https://images.unsplash.com/photo-1721853046219-209921be684e?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8",
},
])
export const pk = writable<string | null>(null)
export const sessions = writable(new Map())
export const session = derived([pk, sessions], ([$pk, $sessions]) => $sessions.get($pk))
export const relayInfo = writable(new Map())
export const groupEvents = deriveEvents(repository, {
filters: [{kinds: [GROUP_META]}],
})
export const groups = derived([relayInfo, groupEvents], ([$relayInfo, $groupEvents]) =>
$groupEvents.filter(e => $relayInfo.get(getGroupUrl(e))?.pubkey === e.pubkey),
)
export const groupsById = derived(groups, $groups => indexBy(getIdentifier, $groups))
export const groupsEvents = deriveEvents(repository, {
filters: [{kinds: [GROUPS]}],
})
export const userGroupsEvent = derived([pk, groupsEvents], ([$pk, $groupsEvents]) =>
$groupsEvents.find(e => e.pubkey === $pk),
)
export const userGroupRelaysByNom = derived(userGroupsEvent, $userGroupsEvent => {
const relaysByNom = new Map()
for (const id of getGroupTagValues($userGroupsEvent?.tags || [])) {
const [relay, nom] = id.split(GROUP_DELIMITER)
pushToMapKey(relaysByNom, nom, relay)
}
return relaysByNom
})

View File

@@ -1,12 +1,17 @@
<script lang="ts">
import {page} from "$app/stores"
export let href
export let active
$: active = $page.route.id?.startsWith(href)
</script>
<a {href} class="button group justify-start border-none transition-all hover:bg-base-100">
<div
class="group-hover:brightness=[1.5] flex items-center gap-3"
class:group-hover:brightness-[1.4]={active}>
<a
{href}
class="button group justify-start border-none transition-all hover:bg-base-100"
class:text-stark-content={active}
class:bg-base-100={active}>
<div class="flex items-center gap-3">
<slot />
</div>
</a>

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,8 @@
import {redirect} from "@sveltejs/kit"
/** @type {import('./$types').LayoutServerLoad} */
export function load({route}) {
if (!route.id) {
redirect(307, "/home")
}
}

View File

@@ -1,12 +1,9 @@
<script lang="ts">
import "@src/app.css"
import {onMount} from "svelte"
import {page} from "$app/stores"
import {onNavigate} from "$app/navigation"
import {fly} from '@lib/transition'
import Icon from "@lib/components/Icon.svelte"
import {toast} from '@app/toast'
import {modals, pushModal} from '@app/modal'
import {fly} from "@lib/transition"
import {toast} from "@app/toast"
import {modals} from "@app/modal"
import PrimaryNav from "@app/components/PrimaryNav.svelte"
import SecondaryNav from "@app/components/SecondaryNav.svelte"

View File

@@ -1,4 +1,3 @@
import * as path from "path"
import adapter from "@sveltejs/adapter-auto"
import {vitePreprocess} from "@sveltejs/vite-plugin-svelte"
@@ -14,10 +13,10 @@ const config = {
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(),
alias: {
'@src': "src",
'@app': "src/app",
'@lib': "src/lib",
'@assets': "src/assets",
"@src": "src",
"@app": "src/app",
"@lib": "src/lib",
"@assets": "src/assets",
},
},
}

View File

@@ -1,3 +1,5 @@
import daisyui from "daisyui"
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,js,svelte,ts}"],
@@ -9,9 +11,7 @@ export default {
"nav-item": 2,
},
},
plugins: [
require("daisyui"),
],
plugins: [daisyui],
daisyui: {
themes: [
"light",