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
|
||||||
10477
package-lock.json
generated
10477
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||||
"@welshman/lib": "^0.0.12",
|
"@welshman/lib": "^0.0.12",
|
||||||
|
"@welshman/net": "^0.0.16",
|
||||||
|
"@welshman/store": "^0.0.1",
|
||||||
|
"@welshman/util": "^0.0.23",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"nostr-login": "^1.5.2",
|
"nostr-login": "^1.5.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.5"
|
"prettier-plugin-tailwindcss": "^0.6.5"
|
||||||
|
|||||||
12
src/app.css
12
src/app.css
@@ -69,18 +69,22 @@
|
|||||||
--stark-content: #111;
|
--stark-content: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-stark {
|
.text-stark,
|
||||||
|
.hover\:text-stark:hover {
|
||||||
color: var(--stark);
|
color: var(--stark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-stark-content {
|
.text-stark-content,
|
||||||
|
.hover\:text-stark-content:hover {
|
||||||
color: var(--stark-content);
|
color: var(--stark-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-stark {
|
.bg-stark,
|
||||||
|
.hover\:bg-stark:hover {
|
||||||
background-color: var(--stark);
|
background-color: var(--stark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-stark-content {
|
.bg-stark-content,
|
||||||
|
.hover\:bg-stark-content:hover {
|
||||||
background-color: var(--stark-content);
|
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">
|
<script lang="ts">
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.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>
|
</script>
|
||||||
|
|
||||||
<div class="relative w-14 bg-base-100">
|
<div class="relative w-14 bg-base-100">
|
||||||
@@ -22,10 +23,12 @@
|
|||||||
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
|
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
|
||||||
</div>
|
</div>
|
||||||
</PrimaryNavItem>
|
</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}>
|
<PrimaryNavItem title={name}>
|
||||||
<div class="w-10 rounded-full border border-solid border-base-300">
|
<div class="w-10 rounded-full border border-solid border-base-300">
|
||||||
<img alt={name} src={picture} />
|
<img alt={name} src={getGroupPicture(event)} />
|
||||||
</div>
|
</div>
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex w-60 flex-col gap-1 bg-base-300 px-2 py-4">
|
<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
|
<Icon icon="home-smile" /> Home
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
<SecondaryNavItem href="/people">
|
<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 type {ComponentType} from "svelte"
|
||||||
import {readable, writable} from "svelte/store"
|
|
||||||
import {randomId} from "@welshman/lib"
|
import {randomId} from "@welshman/lib"
|
||||||
import {pushState} from "$app/navigation"
|
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([
|
export const pk = writable<string | null>(null)
|
||||||
{
|
|
||||||
id: "test",
|
export const sessions = writable(new Map())
|
||||||
name: "Test",
|
|
||||||
picture:
|
export const session = derived([pk, sessions], ([$pk, $sessions]) => $sessions.get($pk))
|
||||||
"https://images.unsplash.com/photo-1721853046219-209921be684e?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8",
|
|
||||||
},
|
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">
|
<script lang="ts">
|
||||||
|
import {page} from "$app/stores"
|
||||||
|
|
||||||
export let href
|
export let href
|
||||||
export let active
|
|
||||||
|
$: active = $page.route.id?.startsWith(href)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a {href} class="button group justify-start border-none transition-all hover:bg-base-100">
|
<a
|
||||||
<div
|
{href}
|
||||||
class="group-hover:brightness=[1.5] flex items-center gap-3"
|
class="button group justify-start border-none transition-all hover:bg-base-100"
|
||||||
class:group-hover:brightness-[1.4]={active}>
|
class:text-stark-content={active}
|
||||||
|
class:bg-base-100={active}>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</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">
|
<script lang="ts">
|
||||||
import "@src/app.css"
|
import "@src/app.css"
|
||||||
import {onMount} from "svelte"
|
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {onNavigate} from "$app/navigation"
|
import {fly} from "@lib/transition"
|
||||||
import {fly} from '@lib/transition'
|
import {toast} from "@app/toast"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import {modals} from "@app/modal"
|
||||||
import {toast} from '@app/toast'
|
|
||||||
import {modals, pushModal} from '@app/modal'
|
|
||||||
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
||||||
import SecondaryNav from "@app/components/SecondaryNav.svelte"
|
import SecondaryNav from "@app/components/SecondaryNav.svelte"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as path from "path"
|
|
||||||
import adapter from "@sveltejs/adapter-auto"
|
import adapter from "@sveltejs/adapter-auto"
|
||||||
import {vitePreprocess} from "@sveltejs/vite-plugin-svelte"
|
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.
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
alias: {
|
alias: {
|
||||||
'@src': "src",
|
"@src": "src",
|
||||||
'@app': "src/app",
|
"@app": "src/app",
|
||||||
'@lib': "src/lib",
|
"@lib": "src/lib",
|
||||||
'@assets': "src/assets",
|
"@assets": "src/assets",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import daisyui from "daisyui"
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
@@ -9,9 +11,7 @@ export default {
|
|||||||
"nav-item": 2,
|
"nav-item": 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [daisyui],
|
||||||
require("daisyui"),
|
|
||||||
],
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
themes: [
|
themes: [
|
||||||
"light",
|
"light",
|
||||||
|
|||||||
Reference in New Issue
Block a user