flotilla/src/routes/spaces/[relay]/recent/+page.svelte

175 lines
5.3 KiB
Svelte
Raw Normal View History

2025-10-06 18:23:19 +00:00
<script lang="ts">
import {onMount} from "svelte"
import {derived} from "svelte/store"
import {page} from "$app/stores"
2026-02-03 17:49:44 +00:00
import {groupBy, ago, MONTH, first, sortBy, uniqBy} from "@welshman/lib"
import {
MESSAGE,
THREAD,
2026-02-03 19:00:13 +00:00
CLASSIFIED,
2026-02-03 17:49:44 +00:00
ZAP_GOAL,
EVENT_TIME,
COMMENT,
2026-02-04 01:09:30 +00:00
getAddress,
2026-02-03 17:49:44 +00:00
getTagValue,
getTagValues,
getIdAndAddress,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {repository} from "@welshman/app"
2025-10-06 18:23:19 +00:00
import History from "@assets/icons/history.svg?dataurl"
2026-02-03 17:49:44 +00:00
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
2025-10-06 18:23:19 +00:00
import {createScroller} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
2026-02-03 17:49:44 +00:00
import Link from "@lib/components/Link.svelte"
2025-10-06 18:23:19 +00:00
import PageBar from "@lib/components/PageBar.svelte"
import PageContent from "@lib/components/PageContent.svelte"
2025-11-13 18:36:28 +00:00
import SpaceMenuButton from "@app/components/SpaceMenuButton.svelte"
2026-02-03 17:49:44 +00:00
import NoteItem from "@app/components/NoteItem.svelte"
import RecentConversation from "@app/components/RecentConversation.svelte"
2026-02-04 01:09:30 +00:00
import ClassifiedStatus from "@app/components/ClassifiedStatus.svelte"
2026-02-03 17:49:44 +00:00
import {decodeRelay, deriveEventsForUrl, CONTENT_KINDS} from "@app/core/state"
2026-02-03 19:00:13 +00:00
import {
makeThreadPath,
makeClassifiedPath,
makeCalendarPath,
makeGoalPath,
} from "@app/util/routes"
2025-10-06 18:23:19 +00:00
const url = decodeRelay($page.params.relay!)
2026-02-03 17:49:44 +00:00
const since = ago(MONTH)
2025-10-06 18:23:19 +00:00
2026-02-02 23:36:50 +00:00
const messages = deriveEventsForUrl(url, [{kinds: [MESSAGE], since}])
2026-02-03 17:49:44 +00:00
const content = deriveEventsForUrl(url, [{kinds: CONTENT_KINDS, since}])
2026-02-02 23:36:50 +00:00
const comments = deriveEventsForUrl(url, [{kinds: [COMMENT], since}])
2025-10-06 18:23:19 +00:00
2026-02-02 23:36:50 +00:00
const recentActivity = derived(
2026-02-03 17:49:44 +00:00
[messages, content, comments],
([$messages, $content, $comments]) => {
2026-02-02 23:36:50 +00:00
const activity: Array<{
2026-02-03 17:49:44 +00:00
type: "message" | "content"
event: TrustedEvent
count: number
2026-02-02 23:36:50 +00:00
timestamp: number
}> = []
2025-10-06 18:23:19 +00:00
2026-02-02 23:36:50 +00:00
const byRoom = groupBy(e => getTagValue("h", e.tags), $messages)
2026-02-03 17:49:44 +00:00
for (const roomMessages of byRoom.values()) {
2026-02-02 23:36:50 +00:00
const latest = first(roomMessages)
if (latest) {
activity.push({
type: "message",
event: latest,
count: roomMessages.length,
2026-02-03 17:49:44 +00:00
timestamp: latest.created_at,
2026-02-02 23:36:50 +00:00
})
2025-10-06 18:23:19 +00:00
}
2026-02-02 23:36:50 +00:00
}
2025-10-06 18:23:19 +00:00
2026-02-03 17:49:44 +00:00
const latestActivityByKey = new Map<string, number>()
2025-10-06 18:23:19 +00:00
2026-02-03 17:49:44 +00:00
for (const event of $content) {
for (const k of getIdAndAddress(event)) {
latestActivityByKey.set(k, Math.max(latestActivityByKey.get(k) || 0, event.created_at))
}
2025-10-06 18:23:19 +00:00
}
2026-02-03 17:49:44 +00:00
for (const event of $comments) {
for (const k of getTagValues(["E", "A"], event.tags)) {
latestActivityByKey.set(k, Math.max(latestActivityByKey.get(k) || 0, event.created_at))
}
2026-02-02 23:36:50 +00:00
}
2025-10-06 18:23:19 +00:00
2026-02-03 17:49:44 +00:00
for (const [address, timestamp] of latestActivityByKey.entries()) {
const event = repository.getEvent(address)
2025-10-06 18:23:19 +00:00
2026-02-03 17:49:44 +00:00
if (event) {
activity.push({type: "content", event, timestamp, count: 1})
}
2025-10-06 18:23:19 +00:00
}
2026-02-03 17:49:44 +00:00
return sortBy(
a => -a.timestamp,
uniqBy(a => a.event.id, activity),
)
2026-02-02 23:36:50 +00:00
},
)
2025-10-06 18:23:19 +00:00
2026-02-02 23:36:50 +00:00
let limit = $state(20)
2025-10-06 18:23:19 +00:00
let element: Element | undefined = $state()
onMount(() => {
const scroller = createScroller({
element: element!,
onScroll: () => {
2026-02-02 23:36:50 +00:00
limit += 10
2025-10-06 18:23:19 +00:00
},
})
return () => scroller.stop()
})
</script>
<PageBar>
{#snippet icon()}
<div class="center">
<Icon icon={History} />
</div>
{/snippet}
{#snippet title()}
<strong>Recent Activity</strong>
{/snippet}
{#snippet action()}
<div class="row-2">
2025-11-13 18:36:28 +00:00
<SpaceMenuButton {url} />
2025-10-06 18:23:19 +00:00
</div>
{/snippet}
</PageBar>
<div bind:this={element}>
<PageContent class="flex flex-col gap-2 p-2 pt-4">
2026-02-02 23:36:50 +00:00
{#if $recentActivity.length === 0}
2026-02-03 17:49:44 +00:00
<p class="flex flex-col items-center py-20 text-center">No recent activity found!</p>
2025-10-06 18:23:19 +00:00
{:else}
2026-02-03 17:49:44 +00:00
{#each $recentActivity.slice(0, limit) as { type, event, count = 0 } (event.id)}
{#if type === "message"}
<RecentConversation {url} {event} {count} />
{:else}
<NoteItem {url} {event}>
{#if event.kind === THREAD}
2026-02-04 01:09:30 +00:00
<Link
href={makeThreadPath(url, event.id)}
class="btn btn-primary btn-xs rounded-full">
2026-02-03 17:49:44 +00:00
View Thread
<Icon icon={AltArrowRight} />
</Link>
2026-02-03 19:00:13 +00:00
{:else if event.kind === CLASSIFIED}
2026-02-04 01:09:30 +00:00
<div class="flex gap-2">
<ClassifiedStatus {event} />
<Link
href={makeClassifiedPath(url, getAddress(event))}
class="btn btn-primary btn-xs rounded-full">
View Listing
<Icon icon={AltArrowRight} />
</Link>
</div>
2026-02-03 17:49:44 +00:00
{:else if event.kind === ZAP_GOAL}
2026-02-04 01:09:30 +00:00
<Link href={makeGoalPath(url, event.id)} class="btn btn-primary btn-xs rounded-full">
2026-02-03 17:49:44 +00:00
View Goal
<Icon icon={AltArrowRight} />
</Link>
{:else if event.kind === EVENT_TIME}
2026-02-04 01:09:30 +00:00
<Link
href={makeCalendarPath(url, getAddress(event))}
class="btn btn-primary btn-xs rounded-full">
2026-02-03 17:49:44 +00:00
View Event
<Icon icon={AltArrowRight} />
</Link>
{/if}
</NoteItem>
2026-02-02 23:36:50 +00:00
{/if}
2025-10-06 18:23:19 +00:00
{/each}
{/if}
</PageContent>
</div>