mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Add some state management stuff
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
package-lock.json -diff
|
||||
141
package-lock.json
generated
141
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
12
src/app.css
12
src/app.css
@@ -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
38
src/app/base.ts
Normal 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
15
src/app/commands.ts
Normal 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
|
||||
})
|
||||
})
|
||||
@@ -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}
|
||||
|
||||
@@ -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
22
src/app/domain.ts
Normal 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]
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
8
src/routes/+layout.server.ts
Normal file
8
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {redirect} from "@sveltejs/kit"
|
||||
|
||||
/** @type {import('./$types').LayoutServerLoad} */
|
||||
export function load({route}) {
|
||||
if (!route.id) {
|
||||
redirect(307, "/home")
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user