import * as React from "react"; import Linkify from "linkify-react"; import { Box, Button, Flex, Text, Flash } from "rimble-ui"; import { Send, KeyboardArrowDown } from "@rimble/icons"; import { VcSchema, JsonSchemaNode, nodeToTypeName } from "vc-schema-tools"; import { baseColors, colors, fonts } from "../../../themes"; import { SchemaDataInput, SchemaDataResponse, defaultSchemaProperties, defaultSchemaPropertiesRequired } from "./types"; import { HighlightedJson } from "../../elements/HighlightedJson/HighlightedJson"; import { ModalWithX } from "../../elements/Modals"; import { SchemaUsage } from "./SchemaUsage"; import { SertoUiContext, SertoUiContextInterface } from "../../../context/SertoUiContext"; import { IssueVcForm } from "../Credentials/IssueVc/IssueVcForm"; import { Popup, PopupGroup } from "../../elements/Popup/Popup"; import { SchemaHeader } from "./SchemaHeader"; const MetadataText: React.FunctionComponent = (props) => ( {props.children} ); const PropertyText: React.FunctionComponent = (props) => ( {props.children} ); const PropertyRequired: React.FunctionComponent = (props) => ( {props.children} ); const PropertyType: React.FunctionComponent = (props) => ( {props.children} ); const PropertyName: React.FunctionComponent = (props) => {props.children}; const renderProperty = (key: string, prop: JsonSchemaNode, required?: boolean): React.ReactNode => { if (!prop) { console.warn("Could not find prop for key:", key); return <>; } let propType: string | undefined; if (prop.type === "object") { // Looks nicer with blank type name - nested object will be displayed underneath propType = ""; } else { propType = nodeToTypeName(prop); if (!propType) { propType = "[unknown]"; console.warn("Unrecognized property:", { key, prop }); } } return ( {prop.title || key} {propType} {prop.description && {prop.description}} {required && Required} {prop.properties && ( {Object.entries(prop.properties).map((entry) => renderProperty(entry[0], entry[1], prop.required?.includes(entry[0])), )} )} ); }; export const SCHEMA_VIEWS = ["Formatted View", "JSON Schema", "JSON-LD Context"] as const; export type SchemaViewTypes = typeof SCHEMA_VIEWS[number]; export interface SchemaPreviewProps { schema: SchemaDataInput | SchemaDataResponse; view: SchemaViewTypes; setView(view: SchemaViewTypes): void; /** Whether to show "view in editor" and "issue VC" functionality. */ hideTools?: boolean; /** Whether to show "issue VC" functionality. */ hideIssueVc?: boolean; fullPage?: boolean; paneView?: boolean; noSwitcher?: boolean; rimbleProps?: { [prop: string]: any }; } export const SchemaPreview: React.FunctionComponent = (props) => { const { schema, view, setView, hideTools, hideIssueVc, fullPage, paneView, noSwitcher, rimbleProps } = props; const [error, setError] = React.useState(""); const [isUseModalOpen, setIsUseModalOpen] = React.useState(false); const context = React.useContext(SertoUiContext); const userOwnsSchema = "creator" in schema && context.schemasService.userData?.sub === schema.creator?.identifier; const schemaInstance = React.useMemo(() => { try { setError(""); return new VcSchema(schema.jsonSchema); } catch (err) { console.error("Failed to generate schema instance:", err); setError(err.message || JSON.stringify(err)); } }, [schema]); const jsons = React.useMemo(() => { if (!schemaInstance) { return {}; } let jsonLdContextString: string; if (schema.ldContextPlus) { // old format with manually created LD context, so use that: jsonLdContextString = schema.ldContext; } else { // new format, with manually created JSON Schema and schema instance will have generated JSON LD context from that: jsonLdContextString = schemaInstance?.getJsonLdContextString(true); } return { [SCHEMA_VIEWS[1]]: schemaInstance?.getJsonSchemaString(true), [SCHEMA_VIEWS[2]]: jsonLdContextString, }; }, [schemaInstance, schema.ldContext, schema.ldContextPlus]); const credentialSubject = React.useMemo(() => { const credentialSubject = schemaInstance?.jsonSchema?.properties?.credentialSubject as JsonSchemaNode; if (!credentialSubject) { console.error("Invalid schema format: could not find `credentialSubject`"); } return credentialSubject; }, [schemaInstance]); return ( {!noSwitcher && ( {SCHEMA_VIEWS.map((viewName) => ( setView(viewName)} className={view === viewName ? "selected" : ""}> {viewName} ))} } > <> {view} )} {!hideTools && context.schemasUiUrl && ( {userOwnsSchema ? "Edit Schema" : "View in Schema Editor"} )} {!hideTools && !hideIssueVc && (context.issueVc ? ( ) : ( } popupRimbleProps={{ width: "350px" }} popupRightPos={-2} > ))} {view === "Formatted View" ? ( {paneView && } {!fullPage && {schema.slug}} Version {schema.version} {!schema.discoverable && "Not "}Searchable {!fullPage && schema.description && ( {schema.description} )} {defaultSchemaProperties.map((prop, i) => renderProperty(prop.$linkedData.term, prop, defaultSchemaPropertiesRequired[i]), )} {credentialSubject?.properties && Object.entries(credentialSubject.properties).map((entry) => renderProperty(entry[0], entry[1], credentialSubject.required?.includes(entry[0])), )} {error && Error: {error}} ) : ( <> {view === "JSON-LD Context" && ( To be used on top of{" "} the base W3C credential context . )} )} setIsUseModalOpen(false)} width={9}> setIsUseModalOpen(false)} /> ); };