fix: show deck skeletons while loading connections
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Icon } from "#/components/icon";
|
import { Icon } from "#/components/icon";
|
||||||
import { Badge } from "#/components/ui/badge";
|
import { Badge } from "#/components/ui/badge";
|
||||||
import { Button } from "#/components/ui/button";
|
import { Button } from "#/components/ui/button";
|
||||||
|
import { Skeleton } from "#/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -22,6 +23,7 @@ import { describeSource } from "./source-description";
|
|||||||
export function ResearchColumn({
|
export function ResearchColumn({
|
||||||
column,
|
column,
|
||||||
connectionLabel,
|
connectionLabel,
|
||||||
|
connectionLoading = false,
|
||||||
onEdit,
|
onEdit,
|
||||||
onRemove,
|
onRemove,
|
||||||
onMove,
|
onMove,
|
||||||
@@ -32,6 +34,7 @@ export function ResearchColumn({
|
|||||||
}: {
|
}: {
|
||||||
column: DeckColumn;
|
column: DeckColumn;
|
||||||
connectionLabel?: string;
|
connectionLabel?: string;
|
||||||
|
connectionLoading?: boolean;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
onMove: (direction: -1 | 1) => void;
|
onMove: (direction: -1 | 1) => void;
|
||||||
@@ -112,9 +115,13 @@ export function ResearchColumn({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="deck-column-label">
|
<div className="deck-column-label">
|
||||||
|
{connectionLoading ? (
|
||||||
|
<Skeleton className="h-3 w-24 shrink-0" aria-hidden="true" />
|
||||||
|
) : (
|
||||||
<span title={`Connection profile: ${connectionLabel ?? "Connection not found"}`}>
|
<span title={`Connection profile: ${connectionLabel ?? "Connection not found"}`}>
|
||||||
{connectionLabel ?? "Connection not found"}
|
{connectionLabel ?? "Connection not found"}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
<Badge variant="outline" className="deck-source-label">
|
<Badge variant="outline" className="deck-source-label">
|
||||||
<Icon name={column.source.kind} />
|
<Icon name={column.source.kind} />
|
||||||
{sourceDescription.label}
|
{sourceDescription.label}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from "#/components/ui/empty";
|
} from "#/components/ui/empty";
|
||||||
import { Input } from "#/components/ui/input";
|
import { Input } from "#/components/ui/input";
|
||||||
|
import { Skeleton } from "#/components/ui/skeleton";
|
||||||
import { loadConnections } from "#/features/connections/server-functions";
|
import { loadConnections } from "#/features/connections/server-functions";
|
||||||
import { syncResearchDeck } from "#/features/research/research-deck-sync";
|
import { syncResearchDeck } from "#/features/research/research-deck-sync";
|
||||||
import { ResearchPanel } from "#/features/research/research-panel";
|
import { ResearchPanel } from "#/features/research/research-panel";
|
||||||
@@ -27,6 +28,7 @@ import { type ColumnRegistry, useColumnTools } from "./column-tools";
|
|||||||
import { ResearchColumn } from "./deck-column";
|
import { ResearchColumn } from "./deck-column";
|
||||||
import { DeckSwitcher } from "./deck-switcher";
|
import { DeckSwitcher } from "./deck-switcher";
|
||||||
import { type DeckColumn, MAX_COLUMNS, moveColumn } from "./model";
|
import { type DeckColumn, MAX_COLUMNS, moveColumn } from "./model";
|
||||||
|
import { ResearchDeckSkeleton } from "./research-deck-skeleton";
|
||||||
import { useDeck } from "./use-deck";
|
import { useDeck } from "./use-deck";
|
||||||
import { useDeckTools } from "./webmcp-tools";
|
import { useDeckTools } from "./webmcp-tools";
|
||||||
|
|
||||||
@@ -202,10 +204,19 @@ export function DeckPage() {
|
|||||||
>
|
>
|
||||||
<div className="deck-heading">
|
<div className="deck-heading">
|
||||||
<div>
|
<div>
|
||||||
|
{ready ? (
|
||||||
|
<>
|
||||||
<h1>{deck.title}</h1>
|
<h1>{deck.title}</h1>
|
||||||
<span className="deck-count">
|
<span className="deck-count">
|
||||||
{deck.columns.length} / {MAX_COLUMNS} columns
|
{deck.columns.length} / {MAX_COLUMNS} columns
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2" aria-hidden="true">
|
||||||
|
<Skeleton className="h-5 w-40" />
|
||||||
|
<Skeleton className="h-3 w-20" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="deck-actions">
|
<div className="deck-actions">
|
||||||
{deck.persisted ? (
|
{deck.persisted ? (
|
||||||
@@ -260,11 +271,6 @@ export function DeckPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{!ready ? (
|
|
||||||
<p className="deck-save-status" role="status">
|
|
||||||
Loading decks…
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{profiles.isError ? (
|
{profiles.isError ? (
|
||||||
<p role="alert" className="deck-error">
|
<p role="alert" className="deck-error">
|
||||||
Unable to load connection profiles.
|
Unable to load connection profiles.
|
||||||
@@ -288,7 +294,6 @@ export function DeckPage() {
|
|||||||
{profiles.data.relayError}
|
{profiles.data.relayError}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{profiles.isPending ? <p role="status">Loading connection profiles…</p> : null}
|
|
||||||
{!renaming && editing === null && !switcherEditing ? storageErrorNotice : null}
|
{!renaming && editing === null && !switcherEditing ? storageErrorNotice : null}
|
||||||
{renaming ? (
|
{renaming ? (
|
||||||
<Dialog title="Rename deck" onClose={() => setRenaming(false)}>
|
<Dialog title="Rename deck" onClose={() => setRenaming(false)}>
|
||||||
@@ -355,6 +360,7 @@ export function DeckPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{!ready && !storageError ? <ResearchDeckSkeleton /> : null}
|
||||||
{ready && deck.columns.length === 0 && editing === null ? (
|
{ready && deck.columns.length === 0 && editing === null ? (
|
||||||
<Empty>
|
<Empty>
|
||||||
<EmptyHeader>
|
<EmptyHeader>
|
||||||
@@ -388,6 +394,7 @@ export function DeckPage() {
|
|||||||
key={`${deck.id}:${column.id}`}
|
key={`${deck.id}:${column.id}`}
|
||||||
registry={registry}
|
registry={registry}
|
||||||
column={column}
|
column={column}
|
||||||
|
connectionLoading={profiles.isPending}
|
||||||
citation={
|
citation={
|
||||||
citationNavigation.target?.deckId === deck.id &&
|
citationNavigation.target?.deckId === deck.id &&
|
||||||
citationNavigation.target.column.id === column.id &&
|
citationNavigation.target.column.id === column.id &&
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Skeleton } from "#/components/ui/skeleton";
|
||||||
|
import { ResearchPostSkeleton } from "./research-post-skeleton";
|
||||||
|
|
||||||
|
export function ResearchDeckSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="deck-board" role="status" aria-label="Loading research">
|
||||||
|
{["first", "second", "third"].map((column) => (
|
||||||
|
<div key={column} className="deck-column" aria-hidden="true">
|
||||||
|
<div className="deck-column-header space-y-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Skeleton className="size-4 shrink-0" />
|
||||||
|
<Skeleton className="h-4 w-32" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-3 w-24" />
|
||||||
|
<Skeleton className="h-3 w-40" />
|
||||||
|
<Skeleton className="h-3 w-20" />
|
||||||
|
</div>
|
||||||
|
<div className="deck-column-results">
|
||||||
|
<ResearchPostSkeleton />
|
||||||
|
<ResearchPostSkeleton />
|
||||||
|
<ResearchPostSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user