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:
|
Acceptance:
|
||||||
|
|
||||||
- The inbox lists each room and conversation with unread activity, naming the
|
- 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"
|
- A conversation with unread activity carries an unread dot, and "Mark all read"
|
||||||
clears every dot on the page.
|
clears every dot on the page.
|
||||||
- Selecting a conversation opens it, and it stays in the inbox once read.
|
- 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.alice, "general")
|
||||||
space.join(user.bob, "general")
|
space.join(user.bob, "general")
|
||||||
space.message(user.bob, "general", "the server is on fire", at(1, HOUR))
|
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")
|
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(conversation).toContainText("General")
|
||||||
await expect(unreadDot(conversation)).toBeVisible()
|
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
|
// Relay health checks had no mount point at all before the dashboard
|
||||||
await expect(page.getByText("Health checks")).toBeVisible()
|
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)
|
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
|
// A conversation stays in the inbox once it's read - it's a list of where things are, not a queue
|
||||||
await conversation.click()
|
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 Button from "@lib/components/Button.svelte"
|
||||||
import HomeSection from "@app/components/HomeSection.svelte"
|
import HomeSection from "@app/components/HomeSection.svelte"
|
||||||
import HomeInboxItem from "@app/components/HomeInboxItem.svelte"
|
import HomeInboxItem from "@app/components/HomeInboxItem.svelte"
|
||||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
import {inboxConversations} from "@app/inbox"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
|
||||||
import {displayContentCount} from "@app/content"
|
|
||||||
import {inboxConversations, inboxSpaceContent} from "@app/inbox"
|
|
||||||
import {navigate} from "@app/modal"
|
import {navigate} from "@app/modal"
|
||||||
import {setChecked} from "@app/notifications"
|
import {allNotifications, setChecked} from "@app/notifications"
|
||||||
import {makeSpacePath} from "@app/routes"
|
|
||||||
|
|
||||||
const conversations = $derived($inboxConversations.slice(0, 8))
|
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 markAllRead = () => setChecked("*")
|
||||||
|
|
||||||
const startChat = () => navigate("/chat")
|
const startChat = () => navigate("/chat")
|
||||||
|
|
@ -31,16 +20,15 @@
|
||||||
|
|
||||||
<HomeSection title="Inbox" icon={Inbox}>
|
<HomeSection title="Inbox" icon={Inbox}>
|
||||||
{#snippet action()}
|
{#snippet action()}
|
||||||
{#if hasUnread}
|
{#if $allNotifications.size > 0}
|
||||||
<Button class="button button-neutral button-xs" onclick={markAllRead}>Mark all read</Button>
|
<Button class="button button-neutral button-xs" onclick={markAllRead}>Mark all read</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/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">
|
<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="font-medium">Nothing in your inbox yet</p>
|
||||||
<p class="max-w-md text-sm opacity-75">
|
<p class="max-w-md text-sm opacity-75">
|
||||||
Rooms and spaces you belong to report their activity here — messages, threads, classifieds,
|
The rooms, space chats and direct messages you belong to show their latest message here.
|
||||||
events and polls.
|
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-wrap justify-center gap-2">
|
<div class="flex flex-wrap justify-center gap-2">
|
||||||
<Link href="/spaces" class="button button-primary button-sm">
|
<Link href="/spaces" class="button button-primary button-sm">
|
||||||
|
|
@ -58,15 +46,6 @@
|
||||||
{#each conversations as conversation (conversation.path)}
|
{#each conversations as conversation (conversation.path)}
|
||||||
<HomeInboxItem {conversation} />
|
<HomeInboxItem {conversation} />
|
||||||
{/each}
|
{/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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</HomeSection>
|
</HomeSection>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import PageBar from "@lib/components/PageBar.svelte"
|
import PageBar from "@lib/components/PageBar.svelte"
|
||||||
import PageContent from "@lib/components/PageContent.svelte"
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
import HomeInbox from "@app/components/HomeInbox.svelte"
|
import HomeInbox from "@app/components/HomeInbox.svelte"
|
||||||
|
import HomeActivity from "@app/components/HomeActivity.svelte"
|
||||||
import HomeNetwork from "@app/components/HomeNetwork.svelte"
|
import HomeNetwork from "@app/components/HomeNetwork.svelte"
|
||||||
import HomeHealthChecks from "@app/components/HomeHealthChecks.svelte"
|
import HomeHealthChecks from "@app/components/HomeHealthChecks.svelte"
|
||||||
import HomeHosting from "@app/components/HomeHosting.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 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">
|
<div class="flex min-w-0 flex-col bg-surface lg:flex-[2] lg:border-r lg:border-line">
|
||||||
<HomeInbox />
|
<HomeInbox />
|
||||||
|
<HomeActivity />
|
||||||
<HomeNetwork />
|
<HomeNetwork />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col border-t border-line lg:w-80 lg:shrink-0 lg:border-t-0">
|
<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