[dyad] Improved schema display - wrote 2 file(s)
This commit is contained in:
146
src/components/pretty-schema-display.tsx
Normal file
146
src/components/pretty-schema-display.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Link as LinkIcon,
|
||||||
|
Type,
|
||||||
|
Image as ImageIcon,
|
||||||
|
User,
|
||||||
|
Building,
|
||||||
|
Calendar,
|
||||||
|
HelpCircle,
|
||||||
|
Hash,
|
||||||
|
Text,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
const keyMappings: { [key: string]: string } = {
|
||||||
|
"@type": "Type",
|
||||||
|
"@context": "Context",
|
||||||
|
"@id": "ID",
|
||||||
|
name: "Name",
|
||||||
|
headline: "Headline",
|
||||||
|
description: "Description",
|
||||||
|
author: "Author",
|
||||||
|
publisher: "Publisher",
|
||||||
|
mainEntityOfPage: "Main Page",
|
||||||
|
image: "Image",
|
||||||
|
datePublished: "Date Published",
|
||||||
|
dateModified: "Date Modified",
|
||||||
|
acceptedAnswer: "Answer",
|
||||||
|
mainEntity: "Main Content",
|
||||||
|
url: "URL",
|
||||||
|
text: "Text",
|
||||||
|
question: "Question",
|
||||||
|
answer: "Answer",
|
||||||
|
logo: "Logo",
|
||||||
|
telephone: "Telephone",
|
||||||
|
email: "Email",
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyIcons: { [key: string]: React.ElementType } = {
|
||||||
|
"@type": Type,
|
||||||
|
"@id": Hash,
|
||||||
|
url: LinkIcon,
|
||||||
|
image: ImageIcon,
|
||||||
|
logo: ImageIcon,
|
||||||
|
author: User,
|
||||||
|
publisher: Building,
|
||||||
|
datePublished: Calendar,
|
||||||
|
dateModified: Calendar,
|
||||||
|
question: HelpCircle,
|
||||||
|
text: Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderValue = (value: any): React.ReactNode => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
if (value.startsWith("http://") || value.startsWith("https://")) {
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={value}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-500 hover:underline break-all inline-flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
||||||
|
<span>
|
||||||
|
{url.hostname}
|
||||||
|
{url.pathname.length > 1 ? "/..." : ""}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return <span className="break-all">{value}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <span className="break-words">{value}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2 pl-4 border-l ml-2 mt-2">
|
||||||
|
{value.map((item, index) => (
|
||||||
|
<div key={index}>{renderValue(item)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <SchemaObjectRenderer data={value} isNested={true} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{String(value)}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SchemaObjectRenderer = ({
|
||||||
|
data,
|
||||||
|
isNested = false,
|
||||||
|
}: {
|
||||||
|
data: any;
|
||||||
|
isNested?: boolean;
|
||||||
|
}) => {
|
||||||
|
const content = (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{Object.entries(data).map(([key, value]) => {
|
||||||
|
if (key === "@context") return null;
|
||||||
|
const label =
|
||||||
|
keyMappings[key] || key.charAt(0).toUpperCase() + key.slice(1);
|
||||||
|
const Icon = keyIcons[key];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className="flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 font-semibold text-sm text-muted-foreground w-full sm:w-40 flex-shrink-0">
|
||||||
|
{Icon && <Icon className="h-4 w-4" />}
|
||||||
|
<span>{label}:</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow text-sm text-foreground pl-6 sm:pl-0">
|
||||||
|
{key === "@type" ? (
|
||||||
|
<Badge variant="outline">{value as string}</Badge>
|
||||||
|
) : (
|
||||||
|
renderValue(value)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isNested) {
|
||||||
|
return (
|
||||||
|
<div className="p-3 border rounded-md bg-background/50">{content}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PrettySchemaDisplay({ schema }: { schema: any }) {
|
||||||
|
return <SchemaObjectRenderer data={schema} />;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { CopyButton } from "./copy-button";
|
import { CopyButton } from "./copy-button";
|
||||||
|
import { PrettySchemaDisplay } from "./pretty-schema-display";
|
||||||
|
|
||||||
interface SchemaDisplayProps {
|
interface SchemaDisplayProps {
|
||||||
schemas: any[];
|
schemas: any[];
|
||||||
@@ -14,27 +15,43 @@ interface SchemaDisplayProps {
|
|||||||
|
|
||||||
export function SchemaDisplay({ schemas }: SchemaDisplayProps) {
|
export function SchemaDisplay({ schemas }: SchemaDisplayProps) {
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center p-4 bg-muted/50 rounded-lg">
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
Found {schemas.length} schema block{schemas.length > 1 ? "s" : ""} on
|
||||||
|
this page.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground">Expand to see details.</p>
|
||||||
|
</div>
|
||||||
|
<Accordion type="single" collapsible className="w-full space-y-4">
|
||||||
{schemas.map((schema, index) => {
|
{schemas.map((schema, index) => {
|
||||||
const schemaType = schema["@type"] || `Schema Block ${index + 1}`;
|
const schemaType = schema["@type"] || `Schema Block ${index + 1}`;
|
||||||
const schemaJson = JSON.stringify(schema, null, 2);
|
const schemaJson = JSON.stringify(schema, null, 2);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem value={`item-${index}`} key={index}>
|
<AccordionItem
|
||||||
<AccordionTrigger>{schemaType}</AccordionTrigger>
|
value={`item-${index}`}
|
||||||
<AccordionContent>
|
key={index}
|
||||||
<div className="relative bg-muted/50 p-4 rounded-md">
|
className="border rounded-lg bg-background shadow-sm"
|
||||||
<div className="absolute top-2 right-2">
|
>
|
||||||
|
<AccordionTrigger className="px-6 hover:no-underline">
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span className="font-semibold">{schemaType}</span>
|
||||||
|
<div className="flex items-center gap-2 mr-2">
|
||||||
|
<span className="text-xs text-muted-foreground hidden sm:inline">
|
||||||
|
Copy Raw JSON
|
||||||
|
</span>
|
||||||
<CopyButton textToCopy={schemaJson} />
|
<CopyButton textToCopy={schemaJson} />
|
||||||
</div>
|
</div>
|
||||||
<pre className="text-sm overflow-x-auto">
|
|
||||||
<code>{schemaJson}</code>
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="p-6 pt-0">
|
||||||
|
<PrettySchemaDisplay schema={schema} />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user