Lay the home dashboard out as full-width sections
This commit is contained in:
parent
d3ceaac1b9
commit
c4934db5ff
10 changed files with 178 additions and 130 deletions
|
|
@ -2,7 +2,6 @@
|
|||
import Stars from "@assets/icons/stars.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Card from "@lib/components/Card.svelte"
|
||||
import type {HealthCheck} from "@app/healthChecks"
|
||||
import {healthChecks} from "@app/healthChecks"
|
||||
|
||||
|
|
@ -15,13 +14,13 @@
|
|||
const apply = () => $healthChecks.apply(healthCheck)
|
||||
</script>
|
||||
|
||||
<Card sm class="bg-surface flex justify-between">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>{healthCheck.title}</strong>
|
||||
<p class="text-sm">{healthCheck.description}</p>
|
||||
<div class="flex items-start justify-between gap-3 px-4 py-3">
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<strong class="text-sm">{healthCheck.title}</strong>
|
||||
<p class="text-sm opacity-75">{healthCheck.description}</p>
|
||||
</div>
|
||||
<Button class="button button-neutral button-sm" onclick={apply}>
|
||||
<Icon icon={Stars} />
|
||||
<Button class="button button-neutral button-sm shrink-0" onclick={apply}>
|
||||
<Icon icon={Stars} size={4} />
|
||||
{healthCheck.action}
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Badge from "@lib/components/Badge.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import HomeSection from "@app/components/HomeSection.svelte"
|
||||
import HealthCheckItem from "@app/components/HealthCheckItem.svelte"
|
||||
import {healthChecks} from "@app/healthChecks"
|
||||
|
||||
|
|
@ -17,30 +18,30 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="card flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<strong class="flex items-center gap-2 text-lg">
|
||||
<Icon icon={Stethoscope} />
|
||||
Health checks
|
||||
</strong>
|
||||
<HomeSection title="Health checks" icon={Stethoscope}>
|
||||
{#snippet action()}
|
||||
{#if $pending.length > 0}
|
||||
<Badge variant="warning">{$pending.length} open</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
{#if $pending.length === 0}
|
||||
<p class="flex items-center gap-2 text-sm opacity-75">
|
||||
<p class="flex items-center gap-2 px-4 pb-4 text-sm opacity-75">
|
||||
<Icon icon={CheckCircle} size={4} />
|
||||
Your connection to the network looks healthy.
|
||||
</p>
|
||||
{:else}
|
||||
{#each $pending as healthCheck (healthCheck.title)}
|
||||
<HealthCheckItem {healthCheck} />
|
||||
{/each}
|
||||
{#if $pending.length > 1}
|
||||
<Button class="button button-primary button-sm" onclick={applyAll}>
|
||||
<Icon icon={Stars} size={4} />
|
||||
Apply all recommendations
|
||||
</Button>
|
||||
{/if}
|
||||
<div class="flex flex-col divide-y divide-line border-t border-line">
|
||||
{#each $pending as healthCheck (healthCheck.title)}
|
||||
<HealthCheckItem {healthCheck} />
|
||||
{/each}
|
||||
{#if $pending.length > 1}
|
||||
<div class="flex justify-center p-3">
|
||||
<Button class="button button-primary button-sm" onclick={applyAll}>
|
||||
<Icon icon={Stars} size={4} />
|
||||
Apply all recommendations
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</HomeSection>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import HomeSection from "@app/components/HomeSection.svelte"
|
||||
import RelayListItem from "@app/components/hosting/RelayListItem.svelte"
|
||||
import RelayCreate from "@app/components/hosting/RelayCreate.svelte"
|
||||
import {user} from "@app/core"
|
||||
|
|
@ -31,30 +32,32 @@
|
|||
</script>
|
||||
|
||||
{#if loaded}
|
||||
<div class="card flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<strong class="flex items-center gap-2 text-lg">
|
||||
<Icon icon={Server} />
|
||||
Hosting
|
||||
</strong>
|
||||
<HomeSection title="Hosting" icon={Server}>
|
||||
{#snippet action()}
|
||||
{#if relays.length > 0}
|
||||
<Link href="/settings/hosting" class="button button-neutral button-xs">Manage</Link>
|
||||
{/if}
|
||||
{/snippet}
|
||||
{#if relays.length > 0}
|
||||
<div class="flex flex-col divide-y divide-line border-y border-line">
|
||||
{#each relays as relay (relay.id)}
|
||||
<RelayListItem {relay} class="px-4 py-3" />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col items-start gap-3 px-4 pt-3 pb-4">
|
||||
<p class="text-sm opacity-75">
|
||||
{#if relays.length > 0}
|
||||
Spin up another hosted space — rooms, calendar and moderation included.
|
||||
{:else}
|
||||
Don't wait for an invite. Flotilla Hosting sets up a space with rooms, calendar and
|
||||
moderation in about a minute.
|
||||
{/if}
|
||||
</p>
|
||||
<Button class="button button-primary button-sm" onclick={openCreate}>
|
||||
<Icon icon={Add} size={4} />
|
||||
{relays.length > 0 ? "Add another space" : "Start a space"}
|
||||
</Button>
|
||||
</div>
|
||||
{#each relays as relay (relay.id)}
|
||||
<RelayListItem {relay} />
|
||||
{/each}
|
||||
<p class="text-sm opacity-75">
|
||||
{#if relays.length > 0}
|
||||
Spin up another hosted space — rooms, calendar and moderation included.
|
||||
{:else}
|
||||
Don't wait for an invite. Flotilla Hosting sets up a space with rooms, calendar and
|
||||
moderation in about a minute.
|
||||
{/if}
|
||||
</p>
|
||||
<Button class="button button-primary button-sm" onclick={openCreate}>
|
||||
<Icon icon={Add} size={4} />
|
||||
{relays.length > 0 ? "Add another space" : "Start a space"}
|
||||
</Button>
|
||||
</div>
|
||||
</HomeSection>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
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"
|
||||
|
|
@ -27,18 +29,14 @@
|
|||
const startChat = () => navigate("/chat")
|
||||
</script>
|
||||
|
||||
<div class="card flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<strong class="flex items-center gap-2 text-lg">
|
||||
<Icon icon={Inbox} />
|
||||
Inbox
|
||||
</strong>
|
||||
<HomeSection title="Inbox" icon={Inbox}>
|
||||
{#snippet action()}
|
||||
{#if hasUnread}
|
||||
<Button class="button button-neutral button-xs" onclick={markAllRead}>Mark all read</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if conversations.length === 0}
|
||||
<div class="flex flex-col items-center gap-3 py-8 text-center">
|
||||
{/snippet}
|
||||
{#if conversations.length === 0 && $inboxSpaceContent.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,
|
||||
|
|
@ -56,16 +54,19 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col divide-y divide-line border-t border-line">
|
||||
{#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}
|
||||
{#each $inboxSpaceContent as { url, countsByKind } (url)}
|
||||
<Link href={makeSpacePath(url)} class="flex flex-wrap items-baseline gap-x-2 text-sm">
|
||||
<strong><RelayName {url} /></strong>
|
||||
<span class="opacity-75">{displayContent(countsByKind)}</span>
|
||||
</Link>
|
||||
{/each}
|
||||
</div>
|
||||
</HomeSection>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import {formatTimestamp, remove, uniq} from "@welshman/lib"
|
||||
import ChatRoundDots from "@assets/icons/chat-round-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileName from "@app/components/ProfileName.svelte"
|
||||
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte"
|
||||
|
|
@ -25,23 +24,22 @@
|
|||
const others = $derived(uniq(remove($user.pubkey, conversation.pubkeys ?? [])))
|
||||
</script>
|
||||
|
||||
<Link href={path} class="card card-sm card-interactive flex flex-col gap-2">
|
||||
<div class="flex min-w-0 items-center gap-2 text-sm">
|
||||
{#if url}
|
||||
<Link href={path} class="flex items-center gap-3 px-4 py-3 hover:bg-surface-more">
|
||||
{#if url}
|
||||
<RelayIcon {url} size={9} class="shrink-0" />
|
||||
{:else}
|
||||
<ProfileCircle pubkey={others[0] || $user.pubkey} size={9} class="shrink-0" />
|
||||
{/if}
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div class="flex min-w-0 items-baseline gap-2 text-sm">
|
||||
<strong class="truncate">
|
||||
{#if h}
|
||||
<RoomName {url} {h} />
|
||||
{:else}
|
||||
Chat
|
||||
{/if}
|
||||
</strong>
|
||||
<span class="truncate opacity-50">
|
||||
<RelayName {url} />
|
||||
</span>
|
||||
{:else}
|
||||
<Icon icon={ChatRoundDots} size={4} class="shrink-0 opacity-50" />
|
||||
<strong class="truncate">
|
||||
{#if others.length === 0}
|
||||
{#if url}
|
||||
{#if h}
|
||||
<RoomName {url} {h} />
|
||||
{:else}
|
||||
Chat
|
||||
{/if}
|
||||
{:else if others.length === 0}
|
||||
Note to self
|
||||
{:else}
|
||||
<ProfileName pubkey={others[0]} />
|
||||
|
|
@ -50,24 +48,29 @@
|
|||
{/if}
|
||||
{/if}
|
||||
</strong>
|
||||
<span class="truncate opacity-50">direct message</span>
|
||||
{/if}
|
||||
<span class="ml-auto flex shrink-0 items-center gap-2 text-xs opacity-50">
|
||||
{formatTimestamp(event.created_at)}
|
||||
<UnreadDot {path} />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<ProfileCircle pubkey={event.pubkey} size={5} />
|
||||
<span class="shrink-0 text-xs opacity-50">
|
||||
{#if event.pubkey === $user.pubkey}
|
||||
You:
|
||||
{:else}
|
||||
<ProfileName pubkey={event.pubkey} {url} />:
|
||||
{/if}
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<NoteContentMinimal {event} {url} singleLine />
|
||||
<span class="truncate opacity-50">
|
||||
{#if url}
|
||||
<RelayName {url} />
|
||||
{:else}
|
||||
direct message
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex min-w-0 items-center gap-1 text-sm opacity-75">
|
||||
<span class="shrink-0">
|
||||
{#if event.pubkey === $user.pubkey}
|
||||
You:
|
||||
{:else}
|
||||
<ProfileName pubkey={event.pubkey} {url} />:
|
||||
{/if}
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<NoteContentMinimal {event} {url} singleLine />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-2 text-xs opacity-50">
|
||||
{formatTimestamp(event.created_at)}
|
||||
<UnreadDot {path} />
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
import {Feeds} from "@welshman/app"
|
||||
import {Scope, feedFromFilter, makeIntersectionFeed, makeScopeFeed} from "@welshman/feeds"
|
||||
import Planet from "@assets/icons/planet.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import HomeSection from "@app/components/HomeSection.svelte"
|
||||
import NoteItem from "@app/components/NoteItem.svelte"
|
||||
import {app, relayLists, user} from "@app/core"
|
||||
import {makeFeedContext} from "@app/feeds"
|
||||
|
|
@ -60,18 +60,14 @@
|
|||
onMount(load)
|
||||
</script>
|
||||
|
||||
<div class="card flex flex-col gap-3">
|
||||
<strong class="flex items-center gap-2 text-lg">
|
||||
<Icon icon={Planet} />
|
||||
Network
|
||||
</strong>
|
||||
<HomeSection title="Network" icon={Planet}>
|
||||
{#if events.length === 0}
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8">
|
||||
<div class="flex justify-center px-4 pb-8">
|
||||
<Spinner {loading}>Looking for notes from people you follow…</Spinner>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center gap-3 py-8 text-center">
|
||||
<div class="flex flex-col items-center gap-3 px-4 pb-8 text-center">
|
||||
<p class="font-medium">Follow a few people to fill this out</p>
|
||||
<p class="max-w-md text-sm opacity-75">
|
||||
Notes from the people you follow collect here. Spaces are a good place to find some.
|
||||
|
|
@ -80,11 +76,13 @@
|
|||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col divide-y divide-line border-t border-line">
|
||||
{#each events.slice(0, limit) as event (event.id)}
|
||||
<NoteItem {event} {context} />
|
||||
<NoteItem {event} {context} class="cv px-4 py-3" />
|
||||
{/each}
|
||||
</div>
|
||||
<Button class="button button-neutral button-sm" onclick={showMore}>Show more</Button>
|
||||
<div class="flex justify-center border-t border-line p-3">
|
||||
<Button class="button button-neutral button-sm" onclick={showMore}>Show more</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</HomeSection>
|
||||
|
|
|
|||
24
src/app/components/HomeSection.svelte
Normal file
24
src/app/components/HomeSection.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
icon: string
|
||||
action?: Snippet
|
||||
children: Snippet
|
||||
}
|
||||
|
||||
const {title, icon, action, children}: Props = $props()
|
||||
</script>
|
||||
|
||||
<section class="flex min-w-0 flex-col border-b border-line last:border-b-0">
|
||||
<div class="flex items-center justify-between gap-3 px-4 pt-4 pb-3">
|
||||
<h2 class="flex items-center gap-2 text-xs font-bold tracking-widest uppercase opacity-75">
|
||||
<Icon {icon} size={4} />
|
||||
{title}
|
||||
</h2>
|
||||
{@render action?.()}
|
||||
</div>
|
||||
{@render children()}
|
||||
</section>
|
||||
|
|
@ -19,9 +19,16 @@
|
|||
children?: Snippet
|
||||
context: FeedContext
|
||||
url?: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {url, event, children, context}: Props = $props()
|
||||
const {
|
||||
url,
|
||||
event,
|
||||
children,
|
||||
context,
|
||||
class: className = "card card-interactive",
|
||||
}: Props = $props()
|
||||
|
||||
const getRelays = () => (url ? [url] : $router.resolver.relays([seen(event)]))
|
||||
|
||||
|
|
@ -34,7 +41,7 @@
|
|||
const onEmoji = (emoji: NativeEmoji) => createReaction({content: emoji.unicode, tags: []})
|
||||
</script>
|
||||
|
||||
<Cv tag={NoteCard} {event} {url} class="card card-interactive">
|
||||
<Cv tag={NoteCard} {event} {url} class={className}>
|
||||
<NoteContent {event} expandMode="inline" />
|
||||
<div class="flex w-full justify-between gap-2">
|
||||
<ReactionSummary
|
||||
|
|
|
|||
|
|
@ -9,16 +9,17 @@
|
|||
|
||||
type Props = {
|
||||
relay: HostedRelay
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {relay}: Props = $props()
|
||||
const {relay, class: className = "card p-3 sm:p-4"}: Props = $props()
|
||||
|
||||
const name = $derived(relay.info_name || relay.subdomain)
|
||||
const host = $derived(canonicalRelayHost(relay))
|
||||
const href = $derived(makeSpacePath(getHostedRelayUrl(relay), "admin"))
|
||||
</script>
|
||||
|
||||
<div class="card flex flex-row items-center justify-between gap-3 p-3 sm:p-4">
|
||||
<div class="flex flex-row items-center justify-between gap-3 {className}">
|
||||
<ImageIcon size={8} alt="" class="rounded-xl" src={relay.info_icon || Server} />
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">{name}</p>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@
|
|||
import {HOSTING_ENABLED} from "@app/hosting"
|
||||
import {PLATFORM_RELAYS} from "@app/env"
|
||||
|
||||
const today = new Date().toLocaleDateString(undefined, {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
|
||||
// A single-space build has no dashboard to show - everything on it is scoped to the one space.
|
||||
onMount(() => {
|
||||
if (PLATFORM_RELAYS.length > 0) {
|
||||
|
|
@ -23,21 +29,26 @@
|
|||
|
||||
<Page>
|
||||
<PageBar>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon={Home} size={6} />
|
||||
<strong>Home</strong>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon={Home} size={6} />
|
||||
<strong>Home</strong>
|
||||
</div>
|
||||
<span class="hidden text-sm opacity-75 sm:inline">{today}</span>
|
||||
</div>
|
||||
</PageBar>
|
||||
<PageContent class="flex flex-col gap-4 p-2 sm:p-4 lg:flex-row lg:items-start">
|
||||
<div class="flex min-w-0 flex-col gap-4 lg:flex-[2]">
|
||||
<HomeInbox />
|
||||
<HomeNetwork />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col gap-4 lg:flex-1">
|
||||
<HomeHealthChecks />
|
||||
{#if HOSTING_ENABLED}
|
||||
<HomeHosting />
|
||||
{/if}
|
||||
<PageContent noPad class="flex flex-col">
|
||||
<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 />
|
||||
<HomeNetwork />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col border-t border-line lg:w-80 lg:shrink-0 lg:border-t-0">
|
||||
<HomeHealthChecks />
|
||||
{#if HOSTING_ENABLED}
|
||||
<HomeHosting />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</PageContent>
|
||||
</Page>
|
||||
|
|
|
|||
Loading…
Reference in a new issue