Phase 2: Refactor Resources cluster to MCP SDK types
Replace ad hoc local types with MCP SDK schema types across the Resources group components and ResourcesScreen, per the v2 UX interfaces plan. - ResourceListItem: accept `resource: Resource | ResourceTemplate` instead of flat `name`/`uri`; render `title ?? name`; delete local `ResourceAnnotations` type - ResourceControls: accept `Resource[]`, `ResourceTemplate[]`, `InspectorResourceSubscription[]` + `selectedUri`/`selectedTemplate` instead of `ResourceItem[]`/`TemplateListItem[]`/`SubscriptionItem[]`; no-op on re-selecting - ResourcePreviewPanel: accept `resource: Resource` + `contents: (TextResourceContents | BlobResourceContents)[]` instead of flat `content: string`; use SDK `Annotations` (audience as Role[]); `lastUpdated` as `Date` not `string` - ResourceTemplatePanel: accept `template: ResourceTemplate` instead of flat props; use SDK `Annotations` - ResourceSubscribedItem: accept `subscription: InspectorResourceSubscription` instead of flat `name`/`lastUpdated`; `lastUpdated` as `Date` - ResourcesScreen: accept `Resource[]`, `ResourceTemplate[]`, `InspectorResourceSubscription[]` + `selectedResourceUri`/ `selectedTemplateUri` + `ReadResourceState`; derive selections via `find()`; add loading/error states - Delete local `ResourceItem`, `TemplateListItem`, `SubscriptionItem`, `SelectedResource`, `SelectedTemplate`, `ResourceAnnotations` types - Update all stories with real SDK fixtures - Update ConnectedView ResourcesActive story Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
61230d533c
commit
d817c46911
2 changed files with 465 additions and 616 deletions
|
|
@ -1,12 +1,12 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import type {
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { InspectorResourceSubscription } from "../../../../../../core/mcp/types.js";
|
||||
import { fn } from "storybook/test";
|
||||
import { ResourcesScreen } from "./ResourcesScreen";
|
||||
import type {
|
||||
ResourceItem,
|
||||
TemplateListItem,
|
||||
SelectedResource,
|
||||
SelectedTemplate,
|
||||
} from "./ResourcesScreen";
|
||||
import type { ReadResourceState } from "./ResourcesScreen";
|
||||
|
||||
const meta: Meta<typeof ResourcesScreen> = {
|
||||
title: "Screens/ResourcesScreen",
|
||||
|
|
@ -28,227 +28,83 @@ const meta: Meta<typeof ResourcesScreen> = {
|
|||
export default meta;
|
||||
type Story = StoryObj<typeof ResourcesScreen>;
|
||||
|
||||
const sampleResources: ResourceItem[] = [
|
||||
const sampleResources: Resource[] = [
|
||||
{
|
||||
name: "config.json",
|
||||
uri: "file:///config.json",
|
||||
annotations: { audience: "developer", priority: 0.8 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "README.md",
|
||||
uri: "file:///README.md",
|
||||
selected: false,
|
||||
annotations: { audience: ["user"], priority: 0.8 },
|
||||
},
|
||||
{ name: "README.md", uri: "file:///README.md" },
|
||||
{
|
||||
name: "schema.sql",
|
||||
uri: "file:///schema.sql",
|
||||
annotations: { priority: 0.5 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "package.json",
|
||||
uri: "file:///package.json",
|
||||
annotations: { audience: "developer" },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "tsconfig.json",
|
||||
uri: "file:///tsconfig.json",
|
||||
annotations: { audience: "developer", priority: 0.3 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: ".env.example",
|
||||
uri: "file:///.env.example",
|
||||
selected: false,
|
||||
annotations: { audience: ["user"] },
|
||||
},
|
||||
{ name: ".env.example", uri: "file:///.env.example" },
|
||||
{
|
||||
name: "docker-compose.yml",
|
||||
uri: "file:///docker-compose.yml",
|
||||
annotations: { priority: 0.6 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "migrations/001_init.sql",
|
||||
uri: "file:///migrations/001_init.sql",
|
||||
annotations: { audience: "developer", priority: 0.4 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "migrations/002_add_users.sql",
|
||||
uri: "file:///migrations/002_add_users.sql",
|
||||
annotations: { audience: "developer", priority: 0.4 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "seeds/users.json",
|
||||
uri: "file:///seeds/users.json",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "seeds/products.json",
|
||||
uri: "file:///seeds/products.json",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "certs/server.pem",
|
||||
uri: "file:///certs/server.pem",
|
||||
annotations: { priority: 0.9 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "logs/access.log",
|
||||
uri: "file:///logs/access.log",
|
||||
annotations: { audience: "application", priority: 0.2 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "logs/error.log",
|
||||
uri: "file:///logs/error.log",
|
||||
annotations: { audience: "application", priority: 0.7 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "api-spec.yaml",
|
||||
uri: "file:///api-spec.yaml",
|
||||
annotations: { audience: "developer" },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "CHANGELOG.md",
|
||||
uri: "file:///CHANGELOG.md",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "LICENSE",
|
||||
uri: "file:///LICENSE",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: ".gitignore",
|
||||
uri: "file:///.gitignore",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Makefile",
|
||||
uri: "file:///Makefile",
|
||||
annotations: { audience: "developer", priority: 0.3 },
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "fixtures/test-data.json",
|
||||
uri: "file:///fixtures/test-data.json",
|
||||
selected: false,
|
||||
},
|
||||
];
|
||||
|
||||
const sampleTemplates: TemplateListItem[] = [
|
||||
const sampleTemplates: ResourceTemplate[] = [
|
||||
{
|
||||
name: "User Profile",
|
||||
uriTemplate: "file:///users/{userId}/profile",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Table Row",
|
||||
title: "Database Table Row",
|
||||
uriTemplate: "db://tables/{tableName}/rows/{rowId}",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Log File",
|
||||
title: "Application Log",
|
||||
uriTemplate: "file:///logs/{service}/{date}.log",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Migration",
|
||||
uriTemplate: "file:///migrations/{version}_{name}.sql",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Config by Environment",
|
||||
title: "Environment Config",
|
||||
uriTemplate: "file:///config/{environment}.json",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "API Endpoint",
|
||||
uriTemplate: "https://api.example.com/{version}/{resource}",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "Report",
|
||||
title: "Generated Report",
|
||||
uriTemplate: "reports://{reportType}/{year}/{month}",
|
||||
selected: false,
|
||||
},
|
||||
];
|
||||
|
||||
const sampleSubscriptions = [
|
||||
const sampleSubscriptions: InspectorResourceSubscription[] = [
|
||||
{
|
||||
name: "config.json",
|
||||
uri: "file:///config.json",
|
||||
lastUpdated: "2026-03-17T10:30:00Z",
|
||||
resource: { name: "config.json", uri: "file:///config.json" },
|
||||
lastUpdated: new Date("2026-03-17T10:30:00Z"),
|
||||
},
|
||||
{
|
||||
name: "schema.sql",
|
||||
uri: "file:///schema.sql",
|
||||
lastUpdated: "2026-03-17T10:28:00Z",
|
||||
},
|
||||
{
|
||||
name: "docker-compose.yml",
|
||||
uri: "file:///docker-compose.yml",
|
||||
lastUpdated: "2026-03-17T09:45:00Z",
|
||||
},
|
||||
{
|
||||
name: "logs/error.log",
|
||||
uri: "file:///logs/error.log",
|
||||
lastUpdated: "2026-03-17T10:31:12Z",
|
||||
},
|
||||
{
|
||||
name: "certs/server.pem",
|
||||
uri: "file:///certs/server.pem",
|
||||
},
|
||||
{
|
||||
name: "api-spec.yaml",
|
||||
uri: "file:///api-spec.yaml",
|
||||
lastUpdated: "2026-03-17T08:15:00Z",
|
||||
},
|
||||
{
|
||||
name: "package.json",
|
||||
uri: "file:///package.json",
|
||||
lastUpdated: "2026-03-17T10:22:00Z",
|
||||
},
|
||||
{
|
||||
name: "seeds/users.json",
|
||||
uri: "file:///seeds/users.json",
|
||||
resource: { name: "schema.sql", uri: "file:///schema.sql" },
|
||||
lastUpdated: new Date("2026-03-17T10:28:00Z"),
|
||||
},
|
||||
];
|
||||
|
||||
const selectedResourceData: SelectedResource = {
|
||||
const readConfigState: ReadResourceState = {
|
||||
status: "ok",
|
||||
uri: "file:///config.json",
|
||||
mimeType: "application/json",
|
||||
annotations: { audience: "developer", priority: 0.8 },
|
||||
content: JSON.stringify(
|
||||
{
|
||||
name: "my-project",
|
||||
version: "1.0.0",
|
||||
settings: { debug: true, logLevel: "info" },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
lastUpdated: "2026-03-17T10:30:00Z",
|
||||
result: {
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///config.json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
name: "my-project",
|
||||
version: "1.0.0",
|
||||
settings: { debug: true, logLevel: "info" },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
lastUpdated: new Date("2026-03-17T10:30:00Z"),
|
||||
isSubscribed: true,
|
||||
};
|
||||
|
||||
const selectedTemplateData: SelectedTemplate = {
|
||||
name: "User Profile",
|
||||
uriTemplate: "file:///users/{userId}/profile",
|
||||
description: "Fetch a user profile by their unique identifier.",
|
||||
};
|
||||
|
||||
export const WithResources: Story = {
|
||||
args: {
|
||||
resources: sampleResources,
|
||||
|
|
@ -257,11 +113,10 @@ export const WithResources: Story = {
|
|||
|
||||
export const ResourceSelected: Story = {
|
||||
args: {
|
||||
resources: sampleResources.map((r) =>
|
||||
r.uri === "file:///config.json" ? { ...r, selected: true } : r,
|
||||
),
|
||||
selectedResource: selectedResourceData,
|
||||
resources: sampleResources,
|
||||
subscriptions: sampleSubscriptions,
|
||||
selectedResourceUri: "file:///config.json",
|
||||
readState: readConfigState,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -275,61 +130,63 @@ export const WithTemplates: Story = {
|
|||
export const TemplateSelected: Story = {
|
||||
args: {
|
||||
resources: sampleResources,
|
||||
templates: sampleTemplates.map((t) =>
|
||||
t.uriTemplate === "file:///users/{userId}/profile"
|
||||
? { ...t, selected: true }
|
||||
: t,
|
||||
),
|
||||
selectedTemplate: selectedTemplateData,
|
||||
templates: sampleTemplates,
|
||||
selectedTemplateUri: "file:///users/{userId}/profile",
|
||||
},
|
||||
};
|
||||
|
||||
export const TemplateWithResource: Story = {
|
||||
args: {
|
||||
resources: sampleResources,
|
||||
templates: sampleTemplates.map((t) =>
|
||||
t.uriTemplate === "file:///users/{userId}/profile"
|
||||
? { ...t, selected: true }
|
||||
: t,
|
||||
),
|
||||
selectedTemplate: selectedTemplateData,
|
||||
selectedResource: {
|
||||
templates: sampleTemplates,
|
||||
selectedTemplateUri: "file:///users/{userId}/profile",
|
||||
readState: {
|
||||
status: "ok",
|
||||
uri: "file:///users/42/profile",
|
||||
mimeType: "application/json",
|
||||
annotations: { audience: "developer", priority: 0.8 },
|
||||
content: JSON.stringify(
|
||||
{ id: 42, name: "Alice", email: "alice@example.com" },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
lastUpdated: "2026-03-17T11:15:00Z",
|
||||
result: {
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///users/42/profile",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{ id: 42, name: "Alice", email: "alice@example.com" },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
isSubscribed: false,
|
||||
},
|
||||
selectedResourceUri: "file:///users/42/profile",
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSections: Story = {
|
||||
args: {
|
||||
resources: sampleResources,
|
||||
templates: sampleTemplates.map((t) =>
|
||||
t.uriTemplate === "file:///users/{userId}/profile"
|
||||
? { ...t, selected: true }
|
||||
: t,
|
||||
),
|
||||
templates: sampleTemplates,
|
||||
subscriptions: sampleSubscriptions,
|
||||
selectedTemplate: selectedTemplateData,
|
||||
selectedResource: {
|
||||
selectedTemplateUri: "file:///users/{userId}/profile",
|
||||
readState: {
|
||||
status: "ok",
|
||||
uri: "file:///users/42/profile",
|
||||
mimeType: "application/json",
|
||||
annotations: { audience: "developer", priority: 0.8 },
|
||||
content: JSON.stringify(
|
||||
{ id: 42, name: "Alice", email: "alice@example.com" },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
lastUpdated: "2026-03-17T11:15:00Z",
|
||||
result: {
|
||||
contents: [
|
||||
{
|
||||
uri: "file:///users/42/profile",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{ id: 42, name: "Alice", email: "alice@example.com" },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
isSubscribed: false,
|
||||
},
|
||||
selectedResourceUri: "file:///users/42/profile",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,39 @@
|
|||
import { Card, Flex, Group, ScrollArea, Stack, Text } from "@mantine/core";
|
||||
import {
|
||||
Alert,
|
||||
Card,
|
||||
Flex,
|
||||
Group,
|
||||
Loader,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import type {
|
||||
ReadResourceResult,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { InspectorResourceSubscription } from "../../../../../../core/mcp/types.js";
|
||||
import { ResourceControls } from "../../groups/ResourceControls/ResourceControls";
|
||||
import { ResourcePreviewPanel } from "../../groups/ResourcePreviewPanel/ResourcePreviewPanel";
|
||||
import { ResourceTemplatePanel } from "../../groups/ResourceTemplatePanel/ResourceTemplatePanel";
|
||||
|
||||
export interface ResourceItem {
|
||||
name: string;
|
||||
uri: string;
|
||||
annotations?: { audience?: string; priority?: number };
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface TemplateListItem {
|
||||
name: string;
|
||||
title?: string;
|
||||
uriTemplate: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface SubscriptionItem {
|
||||
name: string;
|
||||
uri: string;
|
||||
lastUpdated?: string;
|
||||
}
|
||||
|
||||
export interface SelectedResource {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
annotations?: { audience?: string; priority?: number };
|
||||
content: string;
|
||||
lastUpdated?: string;
|
||||
isSubscribed: boolean;
|
||||
}
|
||||
|
||||
export interface SelectedTemplate {
|
||||
name: string;
|
||||
title?: string;
|
||||
uriTemplate: string;
|
||||
description?: string;
|
||||
annotations?: { audience?: string; priority?: number };
|
||||
export interface ReadResourceState {
|
||||
status: "idle" | "pending" | "ok" | "error";
|
||||
uri?: string;
|
||||
result?: ReadResourceResult;
|
||||
error?: string;
|
||||
lastUpdated?: Date;
|
||||
isSubscribed?: boolean;
|
||||
}
|
||||
|
||||
export interface ResourcesScreenProps {
|
||||
resources: ResourceItem[];
|
||||
templates: TemplateListItem[];
|
||||
subscriptions: SubscriptionItem[];
|
||||
selectedResource?: SelectedResource;
|
||||
selectedTemplate?: SelectedTemplate;
|
||||
resources: Resource[];
|
||||
templates: ResourceTemplate[];
|
||||
subscriptions: InspectorResourceSubscription[];
|
||||
selectedResourceUri?: string;
|
||||
selectedTemplateUri?: string;
|
||||
readState?: ReadResourceState;
|
||||
listChanged: boolean;
|
||||
onRefreshList: () => void;
|
||||
onSelectUri: (uri: string) => void;
|
||||
|
|
@ -83,12 +71,16 @@ const EmptyState = Text.withProps({
|
|||
py: "xl",
|
||||
});
|
||||
|
||||
const SCROLL_MAX_HEIGHT =
|
||||
"calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)";
|
||||
|
||||
export function ResourcesScreen({
|
||||
resources,
|
||||
templates,
|
||||
subscriptions,
|
||||
selectedResource,
|
||||
selectedTemplate,
|
||||
selectedResourceUri,
|
||||
selectedTemplateUri,
|
||||
readState,
|
||||
listChanged,
|
||||
onRefreshList,
|
||||
onSelectUri,
|
||||
|
|
@ -97,6 +89,56 @@ export function ResourcesScreen({
|
|||
onSubscribeResource,
|
||||
onUnsubscribeResource,
|
||||
}: ResourcesScreenProps) {
|
||||
const selectedResource = selectedResourceUri
|
||||
? resources.find((r) => r.uri === selectedResourceUri)
|
||||
: undefined;
|
||||
const selectedTemplate = selectedTemplateUri
|
||||
? templates.find((t) => t.uriTemplate === selectedTemplateUri)
|
||||
: undefined;
|
||||
|
||||
function renderReadState() {
|
||||
if (!readState) return null;
|
||||
|
||||
if (readState.status === "pending") {
|
||||
return (
|
||||
<DetailCard>
|
||||
<Stack align="center" py="xl">
|
||||
<Loader size="sm" />
|
||||
<Text c="dimmed">Reading resource...</Text>
|
||||
</Stack>
|
||||
</DetailCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (readState.status === "error") {
|
||||
return (
|
||||
<DetailCard>
|
||||
<Alert color="red" variant="light" title="Read Error">
|
||||
{readState.error ?? "Failed to read resource"}
|
||||
</Alert>
|
||||
</DetailCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (readState.result && selectedResource) {
|
||||
return (
|
||||
<DetailCard>
|
||||
<ResourcePreviewPanel
|
||||
resource={selectedResource}
|
||||
contents={readState.result.contents}
|
||||
lastUpdated={readState.lastUpdated}
|
||||
isSubscribed={readState.isSubscribed ?? false}
|
||||
onRefresh={() => onReadResource(selectedResource.uri)}
|
||||
onSubscribe={() => onSubscribeResource(selectedResource.uri)}
|
||||
onUnsubscribe={() => onUnsubscribeResource(selectedResource.uri)}
|
||||
/>
|
||||
</DetailCard>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
|
|
@ -105,6 +147,8 @@ export function ResourcesScreen({
|
|||
resources={resources}
|
||||
templates={templates}
|
||||
subscriptions={subscriptions}
|
||||
selectedUri={selectedResourceUri}
|
||||
selectedTemplate={selectedTemplateUri}
|
||||
listChanged={listChanged}
|
||||
onRefreshList={onRefreshList}
|
||||
onSelectUri={onSelectUri}
|
||||
|
|
@ -116,33 +160,16 @@ export function ResourcesScreen({
|
|||
|
||||
{selectedTemplate ? (
|
||||
<Group flex={1} gap="md" align="flex-start" wrap="nowrap">
|
||||
<ScrollArea.Autosize
|
||||
flex={1}
|
||||
mah="calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)"
|
||||
>
|
||||
<ScrollArea.Autosize flex={1} mah={SCROLL_MAX_HEIGHT}>
|
||||
<DetailCard>
|
||||
<ResourceTemplatePanel
|
||||
{...selectedTemplate}
|
||||
template={selectedTemplate}
|
||||
onReadResource={onReadResource}
|
||||
/>
|
||||
</DetailCard>
|
||||
</ScrollArea.Autosize>
|
||||
<ScrollArea.Autosize
|
||||
flex={1}
|
||||
mah="calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)"
|
||||
>
|
||||
{selectedResource ? (
|
||||
<DetailCard>
|
||||
<ResourcePreviewPanel
|
||||
{...selectedResource}
|
||||
onRefresh={() => onReadResource(selectedResource.uri)}
|
||||
onSubscribe={() => onSubscribeResource(selectedResource.uri)}
|
||||
onUnsubscribe={() =>
|
||||
onUnsubscribeResource(selectedResource.uri)
|
||||
}
|
||||
/>
|
||||
</DetailCard>
|
||||
) : (
|
||||
<ScrollArea.Autosize flex={1} mah={SCROLL_MAX_HEIGHT}>
|
||||
{renderReadState() ?? (
|
||||
<DetailCard>
|
||||
<EmptyState>Enter a URI and click Read to preview</EmptyState>
|
||||
</DetailCard>
|
||||
|
|
@ -150,18 +177,12 @@ export function ResourcesScreen({
|
|||
</ScrollArea.Autosize>
|
||||
</Group>
|
||||
) : selectedResource ? (
|
||||
<ScrollArea.Autosize
|
||||
flex={1}
|
||||
mah="calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)"
|
||||
>
|
||||
<DetailCard>
|
||||
<ResourcePreviewPanel
|
||||
{...selectedResource}
|
||||
onRefresh={() => onReadResource(selectedResource.uri)}
|
||||
onSubscribe={() => onSubscribeResource(selectedResource.uri)}
|
||||
onUnsubscribe={() => onUnsubscribeResource(selectedResource.uri)}
|
||||
/>
|
||||
</DetailCard>
|
||||
<ScrollArea.Autosize flex={1} mah={SCROLL_MAX_HEIGHT}>
|
||||
{renderReadState() ?? (
|
||||
<DetailCard>
|
||||
<EmptyState>Click to read this resource</EmptyState>
|
||||
</DetailCard>
|
||||
)}
|
||||
</ScrollArea.Autosize>
|
||||
) : (
|
||||
<DetailCard flex={1}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue