diff --git a/open-rpc-docs-react-0.0.0-development.tgz b/open-rpc-docs-react-0.0.0-development.tgz new file mode 100644 index 0000000..d830e46 Binary files /dev/null and b/open-rpc-docs-react-0.0.0-development.tgz differ diff --git a/package-lock.json b/package-lock.json index 001f204..0c8c7c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1906,7 +1906,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1927,12 +1928,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1947,17 +1950,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2074,7 +2080,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2086,6 +2093,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2100,6 +2108,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2107,12 +2116,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2131,6 +2142,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2211,7 +2223,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2223,6 +2236,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2308,7 +2322,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2344,6 +2359,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2363,6 +2379,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2406,12 +2423,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/JSONSchema/JSONSchema.tsx b/src/JSONSchema/JSONSchema.tsx index 9daafc1..f9c021f 100644 --- a/src/JSONSchema/JSONSchema.tsx +++ b/src/JSONSchema/JSONSchema.tsx @@ -1,8 +1,6 @@ import React, { Component } from "react"; -import { Typography } from "@material-ui/core"; import _ from "lodash"; import JSONSchemaFields from "./fields/JSONSchemaFields"; -import PrimitiveField from "./fields/JSONSchemaPrimitiveField"; import { JSONSchema4 } from "json-schema"; interface IProps { @@ -12,44 +10,7 @@ interface IProps { class JSONSchema extends Component { public render() { const { schema } = this.props; - if (!schema) { return null; } - if (_.isEmpty(schema)) { return null; } - if (schema && !schema.properties && schema.oneOf) { - return ( - <> - {schema.oneOf && - <> - one of - {schema.oneOf.map((item) => { - return ( - - ); - })} - - } - - ); - } - let arrayWithItems = schema && schema.type === "array" && (schema.items || schema.contains); - if (arrayWithItems) { - arrayWithItems = _.isArray(arrayWithItems) ? arrayWithItems : [arrayWithItems]; - return ( - <> - array of - - {arrayWithItems.map((item: JSONSchema4) => { - return ( - - ); - })} - - ); - } - if (schema && schema.properties) { - return ; - } - - return ; + return ; } } diff --git a/src/JSONSchema/SchemaRenderer.tsx b/src/JSONSchema/SchemaRenderer.tsx new file mode 100644 index 0000000..da60b84 --- /dev/null +++ b/src/JSONSchema/SchemaRenderer.tsx @@ -0,0 +1,149 @@ +import React from "react"; +import { JSONSchema4 } from "json-schema"; +import { TableRow, TableCell, Typography } from "@material-ui/core"; +import JSONSchemaFields from "./fields/JSONSchemaFields"; +import _ from "lodash"; +import { grey, green, purple, yellow, blue } from "@material-ui/core/colors"; + +interface IProps { + schema: JSONSchema4; + required?: boolean; + name?: string; +} + +const styles = { + cellWidth: { + width: "70px", + }, +}; + +const SchemaRenderer: React.FC = ({ schema, required, name }) => { + if (schema.anyOf) { + return ( + + + {schema.title || name} + + + any of + + + {schema.anyOf.map((p, i) => )} + + + ); + } + if (schema.allOf) { + return ( + + + {schema.title || name} + + + all of + + + {schema.allOf.map((p, i) => )} + + + ); + } + if (schema.oneOf) { + return ( + + + {schema.title || name} + + + one of + + + {schema.oneOf.map((p, i) => )} + + + ); + } + if (schema.items instanceof Array) { + return ( + + + {schema.title || name} + + + array of + + + {schema.items.map((p, i) => )} + + + ); + } + if (schema.items) { + return ( + + + {schema.title || name} + + + array of + + + + + + ); + } + + if (schema.properties) { + return ( + + + {schema.title || name} + + + object + + + {schema.properties && Object.entries(schema.properties).map(([n, prop]: [string, JSONSchema4], i: number) => { + return ( + + ); + })} + + + ); + } + + const colorMap: {[k: string]: string} = { + any: grey[500], + array: blue[300], + boolean: blue[500], + integer: purple[800], + null: yellow[900], + number: purple[500], + string: green[500], + undefined: grey[500], + }; + return ( + + + {schema.title || name} + + {schema.type} + {schema.pattern} + {required ? "true" : "false"} + {schema.description} + + ); +}; + +export default SchemaRenderer; diff --git a/src/JSONSchema/fields/JSONSchemaFields.test.tsx b/src/JSONSchema/fields/JSONSchemaFields.test.tsx index 7dc093a..1bfb298 100644 --- a/src/JSONSchema/fields/JSONSchemaFields.test.tsx +++ b/src/JSONSchema/fields/JSONSchemaFields.test.tsx @@ -12,7 +12,7 @@ it("renders empty with no schema", () => { it("renders empty with empty schema", () => { const div = document.createElement("div"); - ReactDOM.render(, div); + ReactDOM.render(, div); expect(div.innerHTML).toBe(""); ReactDOM.unmountComponentAtNode(div); }); @@ -32,7 +32,7 @@ it("renders with a schema", () => { }, /* tslint:disable */ } as JSONSchema4; - ReactDOM.render(, div); + ReactDOM.render(, div); expect(div.innerHTML.includes("name")).toBe(true); expect(div.innerHTML.includes("string")).toBe(true); @@ -53,8 +53,7 @@ it("renders with a schema required", () => { "name", ], } as JSONSchema4; - ReactDOM.render(, div); - expect(div.innerHTML.includes("Required")).toBe(true); + ReactDOM.render(, div); expect(div.innerHTML.includes("true")).toBe(true); ReactDOM.unmountComponentAtNode(div); @@ -69,10 +68,8 @@ it("renders with a schema without required", () => { }, }, } as JSONSchema4; - ReactDOM.render(, div); - expect(div.innerHTML.includes("Required")).toBe(true); + ReactDOM.render(, div); expect(div.innerHTML.includes("false")).toBe(true); - ReactDOM.unmountComponentAtNode(div); }); @@ -90,10 +87,287 @@ it("renders with a nested schema object", () => { }, }, } as JSONSchema4; - ReactDOM.render(, div); - console.log('div', div.innerHTML); + ReactDOM.render(, div); expect(div.innerHTML.includes("foo")).toBe(true); expect(div.innerHTML.includes("string")).toBe(true); expect(div.innerHTML.includes("object")).toBe(true); ReactDOM.unmountComponentAtNode(div); -}); \ No newline at end of file +}); + +it("renders with a anyOf with nested objects", () => { + const div = document.createElement("div"); + const schema = { + anyOf: [ + { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + }, + { + title: "bar", + properties: { + name: { + type: "object", + properties: { + baz: { + type: "string" + } + } + }, + }, + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("bar")).toBe(true); + expect(div.innerHTML.includes("baz")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a allOf with nested objects", () => { + const div = document.createElement("div"); + const schema = { + allOf: [ + { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + }, + { + title: "bar", + properties: { + name: { + type: "object", + properties: { + baz: { + type: "string" + } + } + }, + }, + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("bar")).toBe(true); + expect(div.innerHTML.includes("baz")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + + +it("renders with a oneOf with nested objects", () => { + const div = document.createElement("div"); + const schema = { + oneOf: [ + { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + }, + { + title: "bar", + properties: { + name: { + type: "object", + properties: { + baz: { + type: "string" + } + } + }, + }, + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("bar")).toBe(true); + expect(div.innerHTML.includes("baz")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a nested arrays of objects", () => { + const div = document.createElement("div"); + const schema = { + title: "MyPotatoObject", + type: "array", + items: [ + { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + }, + { + title: "bar", + properties: { + name: { + type: "object", + properties: { + baz: { + type: "string" + } + } + }, + }, + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("bar")).toBe(true); + expect(div.innerHTML.includes("baz")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + expect(div.innerHTML.includes("string")).toBe(true); + expect(div.innerHTML.includes("MyPotatoObject")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a nested arrays of object", () => { + const div = document.createElement("div"); + const schema = { + title: "MyPotatoObject", + type: "array", + items: { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + } + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("object")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + expect(div.innerHTML.includes("string")).toBe(true); + expect(div.innerHTML.includes("MyPotatoObject")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a nested arrays of object with name passed explicitly", () => { + const div = document.createElement("div"); + const schema = { + type: "array", + items: { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + } + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("object")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + expect(div.innerHTML.includes("string")).toBe(true); + expect(div.innerHTML.includes("My Name")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a nested arrays of objects with name passed explicitly", () => { + const div = document.createElement("div"); + const schema = { + type: "array", + items: [ + { + title: "foo", + properties: { + name: { + type: "object", + properties: { + potato: { + type: "string" + } + } + }, + }, + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("foo")).toBe(true); + expect(div.innerHTML.includes("object")).toBe(true); + expect(div.innerHTML.includes("potato")).toBe(true); + expect(div.innerHTML.includes("string")).toBe(true); + expect(div.innerHTML.includes("My Name")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); + +it("renders with a nested oneOf with nested allOf", () => { + const div = document.createElement("div"); + const schema = { + title: "MyPotatoObject", + oneOf: [ + { + title: "Apple", + allOf: [ + { + title: "Banana", + type: "string" + }, + { + title: "Pear", + type: "string" + }, + ] + } + ] + } as JSONSchema4; + ReactDOM.render(, div); + expect(div.innerHTML.includes("MyPotatoObject")).toBe(true); + expect(div.innerHTML.includes("Apple")).toBe(true); + expect(div.innerHTML.includes("Banana")).toBe(true); + expect(div.innerHTML.includes("string")).toBe(true); + expect(div.innerHTML.includes("Pear")).toBe(true); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/src/JSONSchema/fields/JSONSchemaFields.tsx b/src/JSONSchema/fields/JSONSchemaFields.tsx index e7f4a80..26a9e4a 100644 --- a/src/JSONSchema/fields/JSONSchemaFields.tsx +++ b/src/JSONSchema/fields/JSONSchemaFields.tsx @@ -5,10 +5,9 @@ import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; -import { Typography } from "@material-ui/core"; import _ from "lodash"; -import JSONSchema from "../JSONSchema"; import { JSONSchema4 } from "json-schema"; +import SchemaRenderer from "../SchemaRenderer"; const styles = (theme: Theme) => ({ table: { @@ -18,77 +17,37 @@ const styles = (theme: Theme) => ({ interface IProps extends WithStyles { schema?: JSONSchema4; + name?: string; + required?: boolean; + hideHeader?: boolean; } class JSONSchemaFields extends Component { public render() { - const { schema, classes } = this.props; + const { schema, classes, name, required, hideHeader } = this.props; if (!schema) { return null; } + if (_.isEmpty(schema)) { return null; } return ( <> - {schema.title && {schema.title}} - {schema.description && {schema.description}} - {schema.properties && + {!hideHeader && Name - Type - Pattern - Required - Description + Type + Pattern + Required + Description - {schema.properties && _.map(schema.properties, (prop, name) => { - // support nested objects - if (prop.type === "object") { - return ( - - - {name} - - - object - - - - - - ); - } - if (prop.oneOf) { - return ( - - - {name} - - - one of - - - {prop.oneOf.map((p) => )} - - - ); - } - return ( - - - {name} - - {prop.type} - {prop.pattern} - - {schema.required && schema.required.includes(name) ? "true" : "false"} - - {prop.description} - - ); - })} +
} + {hideHeader && + + } ); } diff --git a/src/JSONSchema/fields/JSONSchemaPrimitiveField.tsx b/src/JSONSchema/fields/JSONSchemaPrimitiveField.tsx deleted file mode 100644 index a03bf8a..0000000 --- a/src/JSONSchema/fields/JSONSchemaPrimitiveField.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { Component } from "react"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableRow from "@material-ui/core/TableRow"; -import { TableHead, Table, withStyles, Theme, WithStyles } from "@material-ui/core"; -import { JSONSchema4 } from "json-schema"; - -const styles = (theme: Theme) => ({ - root: { - background: theme.palette.grey[50], - width: "330px", - }, -}); - -interface IProps extends WithStyles { - schema?: JSONSchema4; -} - -class PrimitiveField extends Component { - public render() { - const { schema, classes } = this.props; - if (!schema) { - return null; - } - return ( - - - - Field - Value - - - - - Type - {schema.type} - - {schema.pattern && - <> - - Pattern - {schema.pattern} - - - } - {schema.enum && - <> - - Enum - {schema.enum.join(" ")} - - - } - -
- - ); - } -} - -export default withStyles(styles)(PrimitiveField);