Split space activity out of the home inbox into its own section
This commit is contained in:
parent
a3d9106a43
commit
7825477155
5 changed files with 70 additions and 27 deletions
|
|
@ -1564,7 +1564,11 @@ that I don't have to walk every space to find out.
|
|||
Acceptance:
|
||||
|
||||
- The inbox lists each room and conversation with unread activity, naming the
|
||||
room and space and showing the latest message, newest first.
|
||||
room and space and showing the latest message, newest first. It holds messages
|
||||
only.
|
||||
- Activity is a row of one card per space, counting what that space has waiting
|
||||
that isn't a message - threads, events, classifieds and the rest. A card
|
||||
disappears once its space is read.
|
||||
- A conversation with unread activity carries an unread dot, and "Mark all read"
|
||||
clears every dot on the page.
|
||||
- Selecting a conversation opens it, and it stays in the inbox once read.
|
||||
|
|
|
|||
|
|
@ -366,6 +366,19 @@ test("US-116 read the home dashboard", async ({seed, as}) => {
|
|||
space.join(user.alice, "general")
|
||||
space.join(user.bob, "general")
|
||||
space.message(user.bob, "general", "the server is on fire", at(1, HOUR))
|
||||
|
||||
space.event(
|
||||
user.bob,
|
||||
() =>
|
||||
space
|
||||
.kind(Thread)
|
||||
.writer()
|
||||
.setRoom(space.url, "general")
|
||||
.setTitle("Bed rotation")
|
||||
.setContent("How often do you move things around?")
|
||||
.renderTemplate(),
|
||||
at(2, HOUR),
|
||||
)
|
||||
})
|
||||
|
||||
const space = scenario.space("space")
|
||||
|
|
@ -378,6 +391,14 @@ test("US-116 read the home dashboard", async ({seed, as}) => {
|
|||
await expect(conversation).toContainText("General")
|
||||
await expect(unreadDot(conversation)).toBeVisible()
|
||||
|
||||
// A space's threads, events and classifieds are counted per space in Activity rather than listed
|
||||
// as conversations, so the inbox stays a list of messages
|
||||
const activity = page.getByRole("link").filter({hasText: "1 thread"})
|
||||
|
||||
await expect(page.getByRole("heading", {name: "Activity"})).toBeVisible()
|
||||
await expect(activity).toBeVisible()
|
||||
await expect(conversation).not.toContainText("1 thread")
|
||||
|
||||
// Relay health checks had no mount point at all before the dashboard
|
||||
await expect(page.getByText("Health checks")).toBeVisible()
|
||||
|
||||
|
|
@ -388,6 +409,9 @@ test("US-116 read the home dashboard", async ({seed, as}) => {
|
|||
|
||||
await expect(unreadDot(conversation)).toHaveCount(0)
|
||||
|
||||
// Unlike a conversation, a space's activity card is a count of what's new, so reading it empties
|
||||
await expect(activity).toHaveCount(0)
|
||||
|
||||
// A conversation stays in the inbox once it's read - it's a list of where things are, not a queue
|
||||
await conversation.click()
|
||||
|
||||
|
|
|
|||
34
src/app/components/HomeActivity.svelte
Normal file
34
src/app/components/HomeActivity.svelte
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts">
|
||||
import {formatTimestamp} from "@welshman/lib"
|
||||
import Feed from "@assets/icons/feed.svg?dataurl"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import HomeSection from "@app/components/HomeSection.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import {displayContentCount} from "@app/content"
|
||||
import {inboxSpaceContent} from "@app/inbox"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
</script>
|
||||
|
||||
{#if $inboxSpaceContent.length > 0}
|
||||
<HomeSection title="Activity" icon={Feed}>
|
||||
<div class="scroll-container flex items-stretch gap-3 overflow-x-auto px-4 pb-4">
|
||||
{#each $inboxSpaceContent as { url, timestamp, countsByKind } (url)}
|
||||
<Link
|
||||
href={makeSpacePath(url)}
|
||||
class="card card-sm card-interactive flex w-56 shrink-0 flex-col gap-2">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<RelayIcon {url} size={8} class="shrink-0" />
|
||||
<strong class="min-w-0 flex-1 truncate"><RelayName {url} /></strong>
|
||||
</div>
|
||||
<div class="flex flex-col text-sm opacity-75">
|
||||
{#each [...countsByKind] as [kind, count] (kind)}
|
||||
<span>{displayContentCount(kind, count)}</span>
|
||||
{/each}
|
||||
</div>
|
||||
<span class="mt-auto text-xs opacity-50">{formatTimestamp(timestamp)}</span>
|
||||
</Link>
|
||||
{/each}
|
||||
</div>
|
||||
</HomeSection>
|
||||
{/if}
|
||||
|
|
@ -7,23 +7,12 @@
|
|||
import Button from "@lib/components/Button.svelte"
|
||||
import HomeSection from "@app/components/HomeSection.svelte"
|
||||
import HomeInboxItem from "@app/components/HomeInboxItem.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import {displayContentCount} from "@app/content"
|
||||
import {inboxConversations, inboxSpaceContent} from "@app/inbox"
|
||||
import {inboxConversations} from "@app/inbox"
|
||||
import {navigate} from "@app/modal"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
import {allNotifications, setChecked} from "@app/notifications"
|
||||
|
||||
const conversations = $derived($inboxConversations.slice(0, 8))
|
||||
|
||||
const hasUnread = $derived(
|
||||
$inboxConversations.some(conversation => conversation.unread) || $inboxSpaceContent.length > 0,
|
||||
)
|
||||
|
||||
const displayContent = (countsByKind: Map<number, number>) =>
|
||||
[...countsByKind].map(([kind, count]) => displayContentCount(kind, count)).join(" · ")
|
||||
|
||||
const markAllRead = () => setChecked("*")
|
||||
|
||||
const startChat = () => navigate("/chat")
|
||||
|
|
@ -31,16 +20,15 @@
|
|||
|
||||
<HomeSection title="Inbox" icon={Inbox}>
|
||||
{#snippet action()}
|
||||
{#if hasUnread}
|
||||
{#if $allNotifications.size > 0}
|
||||
<Button class="button button-neutral button-xs" onclick={markAllRead}>Mark all read</Button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
{#if conversations.length === 0 && $inboxSpaceContent.length === 0}
|
||||
{#if conversations.length === 0}
|
||||
<div class="flex flex-col items-center gap-3 px-4 pb-8 text-center">
|
||||
<p class="font-medium">Nothing in your inbox yet</p>
|
||||
<p class="max-w-md text-sm opacity-75">
|
||||
Rooms and spaces you belong to report their activity here — messages, threads, classifieds,
|
||||
events and polls.
|
||||
The rooms, space chats and direct messages you belong to show their latest message here.
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
<Link href="/spaces" class="button button-primary button-sm">
|
||||
|
|
@ -58,15 +46,6 @@
|
|||
{#each conversations as conversation (conversation.path)}
|
||||
<HomeInboxItem {conversation} />
|
||||
{/each}
|
||||
{#each $inboxSpaceContent as { url, countsByKind } (url)}
|
||||
<Link
|
||||
href={makeSpacePath(url)}
|
||||
class="flex items-center gap-3 px-4 py-3 text-sm hover:bg-surface-more">
|
||||
<RelayIcon {url} size={6} class="shrink-0" />
|
||||
<strong class="truncate"><RelayName {url} /></strong>
|
||||
<span class="truncate opacity-75">{displayContent(countsByKind)}</span>
|
||||
</Link>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</HomeSection>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import HomeInbox from "@app/components/HomeInbox.svelte"
|
||||
import HomeActivity from "@app/components/HomeActivity.svelte"
|
||||
import HomeNetwork from "@app/components/HomeNetwork.svelte"
|
||||
import HomeHealthChecks from "@app/components/HomeHealthChecks.svelte"
|
||||
import HomeHosting from "@app/components/HomeHosting.svelte"
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
<div class="flex min-w-0 flex-col lg:flex-row lg:items-stretch">
|
||||
<div class="flex min-w-0 flex-col bg-surface lg:flex-[2] lg:border-r lg:border-line">
|
||||
<HomeInbox />
|
||||
<HomeActivity />
|
||||
<HomeNetwork />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col border-t border-line lg:w-80 lg:shrink-0 lg:border-t-0">
|
||||
|
|
|
|||
Loading…
Reference in a new issue