From ca9ce9d376265838ca4b61f4b2801f501c43dc62 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Wed, 15 Jan 2020 15:51:59 -0800 Subject: [PATCH 1/3] fix: handle recursive json schema --- package-lock.json | 41 ++- src/JSONSchema/JSONSchema.tsx | 41 +-- src/JSONSchema/SchemaRenderer.tsx | 135 ++++++++ .../fields/JSONSchemaFields.test.tsx | 298 +++++++++++++++++- src/JSONSchema/fields/JSONSchemaFields.tsx | 71 +---- .../fields/JSONSchemaPrimitiveField.tsx | 61 ---- 6 files changed, 467 insertions(+), 180 deletions(-) create mode 100644 src/JSONSchema/SchemaRenderer.tsx delete mode 100644 src/JSONSchema/fields/JSONSchemaPrimitiveField.tsx 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..943f2eb --- /dev/null +++ b/src/JSONSchema/SchemaRenderer.tsx @@ -0,0 +1,135 @@ +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"; + +interface IProps { + schema: JSONSchema4; + required?: boolean; + name?: string; +} + +const styles = { + cellWidth: { + margin: "5px", + padding: "5px", + width: "70px", + }, +}; + +const SchemaRenderer: React.FC = ({ schema, required, name }) => { + if (schema.type === "object" || schema.properties) { + return ( + + + {schema.title || name} + + + object + + + {schema.properties && Object.entries(schema.properties).map(([n, prop]: [string, JSONSchema4], i: number) => { + return ( + + ); + })} + + + ); + } + 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.type === "array" && schema.items instanceof Array) { + return ( + + + {schema.title || name} + + + array of + + + {schema.items.map((p, i) => )} + + + ); + } + if (schema.type === "array" && schema.items) { + return ( + + + {schema.title || name} + + + array of + + + + + + ); + } + return ( + + + {schema.title || name} + + {schema.type} + {schema.pattern} + {required ? "required" : "optional"} + {schema.description} + + ); +}; + +export default SchemaRenderer; diff --git a/src/JSONSchema/fields/JSONSchemaFields.test.tsx b/src/JSONSchema/fields/JSONSchemaFields.test.tsx index 7dc093a..aef3596 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,9 +53,8 @@ it("renders with a schema required", () => { "name", ], } as JSONSchema4; - ReactDOM.render(, div); - expect(div.innerHTML.includes("Required")).toBe(true); - expect(div.innerHTML.includes("true")).toBe(true); + ReactDOM.render(, div); + expect(div.innerHTML.includes("required")).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); - expect(div.innerHTML.includes("false")).toBe(true); - + ReactDOM.render(, div); + expect(div.innerHTML.includes("optional")).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); From cd0387dc5b7a2a3d1f42d31da3ff223d3b704fbe Mon Sep 17 00:00:00 2001 From: shanejonas Date: Wed, 15 Jan 2020 17:43:16 -0800 Subject: [PATCH 2/3] fix: add color map for json schema types --- open-rpc-docs-react-0.0.0-development.tgz | Bin 0 -> 16497 bytes src/JSONSchema/SchemaRenderer.tsx | 76 +++++++++++++--------- 2 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 open-rpc-docs-react-0.0.0-development.tgz 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 0000000000000000000000000000000000000000..d830e467449683db1c5265c2de8e583fcd62e4d5 GIT binary patch literal 16497 zcmY+rWmFwO(=Cjc0o>x$nEj8c*HPJ3kn{+_#;vxvqkBtumo$jrMrsj!RML zm-l1V-_z4V8X05pcrWVY{?mH^!uCW7jrEp5FBcO~lp+BGYsf~3W{wb)Ec|RZH8V1? z>QqcO<;yCje+Tn3vHQEEU!33UJJnlkMCsCLRi9`rG%$JjGdos52;DGabGHPGLrf4`pd1M*Ih<0M2l2|*wYtc-$8K^^vcUh z_XbC?Q&w>8c0EZKe;wrAp0lui` ziVN0??N0o-I3ceJH-3_JKJV>lRHkJ^K>@&?3od2X0{^tNgj@p`|X8zFfeAZf5;Pg5m(5d z^UtehmU|_BUY+F%_qcL|SG~3%?)o>u-T{kB{;SM4oo@Zk%5PBdkl1U& z!{TUOpAKYhvC^V8G~DjEx6{dL?3AxB9@~h}?-L(EnefX-3{KO3HZ=7~d&%kW38TJb zpCSoD%GV(nDVGY?iSjRXQY#JynZY1Wa^f%V^?STSCDP-aRM`?pVxLH*{V>kn@8)x( z&&;DhHB}~aV1Y?%J-VH8KMFP}=O%Rz?E^In%#KA$ZhKM;YAAa~nH8WO8iXxH^5=sv z&lYx@+F#x_!J#SVkmLBTE@5o=#Vx{MW!@h5tX7YJbs~G1gJ-e#l|PX?cIym6xy*xE zY8({gaf#JkO%NWZbGC*~5$q+%sblmC!l86+*t8P#u@)1fSrmKl*TJTii(1uUNp=zo z)DH)4cdASJmFqga!h$Je<4f{rktp<6QI&Fg4%$i4OSSg?Yf&)aSN$4T*>-=If(8E? zGwQBM1Qpq|XL?1sz-%Jv^?ASKn=t?S?Df-SZs?7`7ZRhCWOWk*I@YoYRWf;r0ptpx zwWd1d8pY0H{I$Bx)$a69*jXs}WUwZdgWQQ*bOpzj+~Rp-@g+KrzE$K*xi@vXuG|d3 zC8n|jI@y;;!c8HRf7qd^x$|`{nt_=r^s9b6#HV$OG|OW`QyfB;V*h@9>FEdmz|mn_ znN_Ry+ijuehLH7_nAB2zDiZ4-n^=~L8mKw$`h3IRK1;iWa%UgyV6i5&cy}AfJU=kY%r!70YaO zRixD7e6E8=md5I*sbV8dZZDJ>$7vuja&=1lZJmvw#dT{)2#;9S{N|Ey#6+a8-BuEs zVJ>!PhQ&rx^Mfy^n4CNjb9{@ktXFBXuc(?g>Ve`|;7s!dmPB;^#GwjPq*begm?du& z=(z^{8D{csSkXfx&V=o82A`oyGr6Mxxz_5DXg)8OWm2~JME>R> zwL7~nQm7#%sG?A%l7u973rRAF{XW$!iGWW>myKH<1&{OC zO7aUoHdJVqr6sh^!%{pq@|k0R8O^%Oly6{U)>yRG<{jF$P=Z$q<3*1=CO~}Rn z;ZZPB<8kA=?#N=kxzV>+gcbC(*01+h_)BII$e0iwp&ec6@y}*Dbur8{>%36e8FU;g z5Iu>IpY#r%ryzc=4mUjmWH}zdCIIgPptlN?UnFvo6h>95OI`-vm1#sqb28cOp^?-( zhkTg$6^zwIuzkb(u~~ptN~I|Gt8FY3l|AWe#1HI5MDh2zh8eDkIsNZtoxEB+=2o6YbhN9LGFTFS}iTVu#@ZY>FiP&0U`6XEgV@6f%j&R>O?NiQB{lgQV- zJqBVoqtAEs$g=t}OamBFW$#@eA+ldY|Cz?SE|?(hm#wTLFvX72ZZhv}WD`_e=v%nF z-tTQHhE2g5+c!=Y2=NYjUqcPw*dWMequ=!02CB26#Wv4A8Oqd3yHLR{?_gxUK@A?X zP*(SgIJ&HZ-rmEU8c)eSSmC|^G&Pee@o018?GT2^A}Eq4vT&l0sjDt)A>A3K1+6P& zM;&(e5pl(=YtE9a7+bIM#dFonLuMXij_KG&LuMoE-{rRCg2;i?CV#9rK&zR!>{dB7 zA0k&T`8D_^1&>}L`Q|kxw(~BD4$yqOTPXH&m#5(8ox%INb$ZC#5G%kunHW4gU0ZGhbn_0>GH@dh);j|*&K!a&YNLuGcGQgaeWpmT2_&E++dbDvvvLdtX zN-RPZGXd-ccw<Dj>(A~~5p?j}VfPs@DGfkn#YF_LX>WWk!eHXD7xgaX7O^7O z;@xI;B~&=75&JpKH1k=+F1W2ePTK24pqyk2qsSz(-vov#E*T0YQboUpcvOLrZ^-4x zH-h{S7|zi?X7y}HHPP$T^VOM<89)>r1%Rn!i-CI3ikA7zG1@YTnK&9N808=>vOmvh z$ZM_k0BOv-3Co*<#aQ|M+dM6aw$aZ|U6O8_Ivqhpm_rgXcN)6uDo5=U-<@ufdtmYV zll62H2F%tR!e?)oFFtHqvu0tDkrnm?sFJtT7U=G#o7;FCw6J8*GRFq$;7DLpt~xoi zlW&IBFiqpw7)vMqTrjtz8PDcYk1PEvaP*qTz;f-3Y_qpew1DOKm9du%Nqz+H);P*J zM)@JB(R5r&(Q9>pguj}MstC6kkeTOASWQ^``%xw?`K$sDCvc~&LqH(!C`uDgwTnjV ztL!&Q5#d^~4qjC!bn6|#u||hD)EVE(xM7;wPV8woozkN^53~1y6N>MqNEJvJjRP+N zH?Y;kp3`C~${tJ|d1}ILLm6&N9TVM**SgyHhXuw*`^zt3ftl`?B__4d=E^Tu!RCjt z2+{@Q9ew?{`@8Nx{j+geMbd;ESN;^q z9}#fw_|Z&L(gk8BCHQ`RN+qC4eH{K(l)JZ|Z5pAq*I-7J)X=!=&tBM{UTUSSxMJH7 z>?%K6M8N$P&?NRR~097iptd(6S#JQ9@9_ zf1*eta%PB%j|#<^hh}39w<|FFNG{k5Y%hNzz+P~C~Yu+fh25bG@IabR~o z)6$-60WvSMwZ9o_3DcmDzQVZ5vtx1wKU9Tw1?YEQFVe&IKBpc0xd=3EyV0~$_;*{H z{+lKb9IZ@D<1UwKWSmjnwg5gEM-kz3&q+LcXH*WUj<3K4-im2m7PK2R2vM4i!ax2l z*c~}a#qNums|+Rh1GY1jRDQd;9!nu|fYtIvndwk=$V5m#gavuOjblX!g4S!Q2N1{Tkt`C!Q`5J6qvwx&BmC^4xT_o`SS}_rFxcO;t1!D9eg+jHkk$ z?%BM&pC&RIoqlN!m9pwj%mbcnSv6U_Yy_e4>ymRw$^%i}x?U_!-W}dKT_DxDrf*#d z|11hfy#_cS2AE!_NrwPDIr2BMnBU`@zoG-rtCQBc!IeEaM-m=%vdgQ(=dyuu#}MlM z)pU|1CINd7YkYP?zoVM^zU_Bv(4I8SvHZ)QD4V1~O2B|TD`BolZD@v86!|k!tJ>mM zL!v#BONEjjuBBX`3J=UK1I|Ar%{Rukg|cILQgT0eA{+hSV=3GH{p%5VU%~o|OOH{7 z3A=^p^0US*xqM$$ACQ~aFCZyT2voDq0P?HT zcrO96ttOPZf3yu{d(y6At~G$*!&qo6McC-yu4KPg?ng1izB+!usg0iI7-|`3XHL9P zjXW*eibj!_!07g~la55!D|@nbvs2&v&7x&nE5~`mP^Bg0r2ISH?dHBRX#IGJE=ys4 zDC1+4Bv&8s7hOh>=;+BUFp$cO#vIkQj7S#QIvA=}ZD^F|ky71Kdw#(Q-E0=goCta3 zk5Gg!ROBCG3$|>OU#$q=t8{6`Vk1T!&Txuwf(N4v0h<3TwH27-qdcF5E=_o=MA`m+;|X6(Z6>_7IYKKq9QTWvEKEP@Rh@-c z*b4Du?9X#WS+0kfAYvw=qax+J?GO}UME0ssEVd_2g$a~nv-)xKs zbi-KQZDJhIt6gB>c#OU*{*l@wCYI7px7R~d?)vIve{vE^{yNxDZ#5fjB_&Da5B9*Mt)DX|ALlO_Iqve2c}Xk6~k<(Cv!5% z8s6ecJ7}qx+Fx}yny24TVd!=anISMz+*cb|`iB0eCQrCsG;#!Yc9W3Y9OfY!#s+K z{Z053$y^$IscZ33oier|?jy5W+kk=oRgM}WO)a<6a!H#-gMD(3vGYKl#mo^o#yOP?GmUCAkCl;ZrO#g#f#!5XtxiZah_vJyK>V{=+6olL|9|l3 zzkkd3|NUpQd!N9**ARn&fbPAsf1N(_zra*5(!uD_cYtDt3r(_$;+Z<6y89wf?GkM~ zsZ!YyVjDsEa{t@Ol|Y$;LHH8dU@$qMc<5M;=;Yg<=guS}sSpXEqc+qG=CSxVsU&p^ zeA9f_#-*PbD^&Wjm3;p5%Xi0(&>I)tYrw1cP+ak?Yc6*N;E9g{EdF_P@io8&$!Wg> zDo)L(i7&S+t`lYis%|T;n`Ra5y_qRq>;u?kd!3L&IR0rLD;Q)El}E_wmN|?SO>!R=uk)lwbR+70f^ai>3kldT22s2VHU-g`?2BA@$Ym9-B-~fCVzt z{>3{Jw2a4=MHJdfbhOC1R?MWpH*DzRO#E<*%(+}#N3>e75zAZ1K1$K_9f7Y-_BAyzEXOWbWDY*ZW|T<_Z+6FQ<->i!>}Txi=SdZz0Axy45^!P@t9zdLbB z@O7#Bi`*uXfn>>ux~@tv)zTHoCQ$)GkWc|wGHwrga%6Pa|7axykNzPI9I?Gj^$Wzp z7~~_LS6_y8tp4z^H($AXCN4QK_dgs}ZtBy2T0yP+-&PLA$VA!kpik6K(e*QL9BAn} z7^a3O9tu!$td2zRa%z&=zf_l965O_Ic1`5!-U#Y{YMXWUzJk>rL6}Of2arRlgc*Qt zGU1`HgRcIPSwfOb{Eh!xOO#y>P6JAu_&TOw_M{2ZVL4rIU(vZot!V-~3533iuGjM|x+o)MK}@J^3*-PZufyM+Rm7{AA4l z#=BFYYB`P=?y5Z=x3%P{3_Tt;+{@?VbRQIFJAXD8eVC#bm&i|*nfN038rG1V&$h5; zy2j^c)2VO=`kJAq3ULIVHi(?Bh4~ls)2zr^7BKV6qX}5(v8hy+`&XSTm|?g`ipyy( zxd^bKm!Q?$u8OV(CEvaXn@}3nL&+?ud~D*Q-JHWwV|R3cpsa z?(}mtp>2(SDGhv8@nw5L(U=x%gL8%Zbl9U=M3~%*_4z!&lkRdS{^z6UnUVYwCX79f znHs5gj--=XHq)#fKCW>jCroTZcD=sEdS2qm0#90>&-{vE(_&Z(egoyrPz}U7Bjsm7 z>Ykky-Z67Z^)QNBWERdBBD#$}+3zo1ikB}lg)r}1$Iqo@07;C(+miWgsx4r5p=)Go zJjTwZZnL(Ny_h)R*AB{)_Mi)0-+3n;6Xfeyuch57_3BCu^7KN(KHg`Wk;ssmX~Ya( z$!?)TOfI*Z$ch~iDKjr_9SmuOQ?_~4j5QUzeH7|$JM;_;6bQx8(1d5s29cM z9$K*`!K~6=z74A}_fK?bUOFd3Ox(1vX)((yWMwD=x@>T+Z4aM9cJ)VUJ2IgSqwhX1Q}e0%A~ z_%@l!uy>WhbOC|(g75ZJ%bXK+=0}z(s=LaJBtdm|k8$#@Psek`Rf^7_FLLvbxdNY& zkIEu_9xC6SF~0%DZU-CM=vuCj{M1<>TFF}qSd!s@fMx&L!CRNpm$gDz?YfdT7^4Cd z*sBSdD%{32giTsEGz53Qq@_P+cG%$Z#+L^hNskxqg_v%Nrqq( z(vr;^wIH@Xp**6;nl;TGpKOXuZJRLRyCX)2PT98^>fvB;<`(`4HMm^XHao9PhRFXTeMi;{GiI| z<0G0e%gSjs3J~dBDn39z-vG~0{bt`Gpv$bxCs6c1fl2Wn*bu*0&j1%{K>LW*Mw{{A zBH>YE_EUwMbX_IYTg9pJxnn$L$U%6d%JDW?yiMdq&9S@k-WdsLNzBnme=zr^A+|ZW z;j>d&wo^9Z(ZfxW0V6Agyas&cIT{k4R4q}!!P+180A!m9Qj?3gEHYmShg5S|k;18` z>X=5yqF_f^c}h~7H^J}Y!THl4VnxJdmHyGoUSxwn%43KEu#TL z5z*%>&y?&iX=dfFOk)mJ!#GCEUlSxc10B^P0e!A)fsZReKE4SybU|M?bhUc=Nmp)} zDd$~(<>rOz!DnVm^9W85-LnbUR95M?#nAkP<*?Jt=Ze5(IU1|NcdhX_f>Y&n8qBYK z4LHnJ$IOPp@Zj>TD{kJ9J}XK%BuT)GFP5`s@?uSCz#R*sDGfc3&&xGfYxm_e5fh{_ z)3!K4xn2!Ro7d|7tV|LVRBYTfk%7(r7MQNuS;SXNv7&lms(Lb+$t6@srwqw7POUYL zl?r(#(Ydz>b`4AGss-GhER_p>vnC8dH`=P57?338aoQEz&bVcoN2=r-MTRgFH(3%j zdH%MICttQqU``S4HCp~e-G`5^!)06R)HLjwi=Mqs?Y~yl`YFe?+F>hU@{g(oB9;E^ zr=42dTr5YtH$BC};leht+a(bPR0 zBA?$dkAHwq&tIBayA`b6)avz^`0}c{;eAn<-LsIhC7atbugpbGXT^U_ttS(y*GZB- zO~7f=@=1=7H|ZhrHBQ8wVn;wiBuP;sWlk%@TkXJszLJd(%H_$CTyq*WT;mAEf$u91 zS783L-%%d1ovu#%4)Gd8wR#Jhh@8*m6ZghWEo`%oqZ|FzDe+%|>a(NT!)r}u2TR|c ztD6JrT|XUCeY$NN#jXi_0v$Rojm^`G<$l*R6u{f!Bd$|0CR@BiH>H9~=J>7N` z|NLS4iQq2Tr>lcpo6&*v+`9!D*Hr9R_!7yD@nl4lc&55QyLqBRvs)-CQ^We*R9mPA zjLX+7e%(4kzFW!p-j;f806mI@tgE1@g~nUTY}SHcLM!$5)7pISV%!F;zs;;snvIyyxucq%)4l$1(3&72t z_pGw5FF6pyqzGV+;z4QRo%KHmCHoJd<|~07Z=7*N?qCT6dar0x2clGy38Su`>*q54 zNV26w=3XSq3;+5PPIJbrUE#Jg2O(x5U9KGqdZmKFaLtfEFt)2@*9g4B#O zC%sB6drSl8HL{5*CGctIXk?k7I7vb3-F)6bVaBcf<}l!o_SdDZ0h-gLjD?aB@8=(8 z-g8U3=$aTQu)l7oKev5ra2BAH7kpy72mLVH9lq|!w8WOp7gtE82`unvr*n4Sx4@Yu zfu~Sa!RxE??!CEIris|3$i379yQ)C@s*wJVFfQaMnuotofV*`M`%Rp9#|-b*2J!D3 z&tgdL5X_4DP-5WE3EjJA3qe=0JkU8tC>UReX?uU*Q+MFa@(FURFKe44a%YBkZN|oz2Jd$B=Hdv(T7r75|g=1KD;j;JmL}Ydi&@~FwC+C z)5^IH&m@d=J~gO>=I~b^dDW~gaap2N0mb|{VPs6NH17DoLU5vXtj*WXLvJ;{(dDCM z;&U}d7Gsz330NG7Zx4EIzqs&jvO#T~>gK1ckVM1jxdc(=4Y|{6J0*5}k7oTnu_P+R zC+9|%Mfo%j&w#3e9F}UjWy|6ii(dGP;bnm!J%NFZS$$+Kl&Ko6&(7Iz8DZ)q*Tyik z2xFk;zB}(fAY8j{L|4JV-XkgY)fHOMAfU4*Ztv%YhNbtHvPoAV)-&SjGD(9~$Ht;! z&q41)qi3H6XF?n?fVdeiSig3dcD(Jp-4sGZ@jt2E4mfY-*>w{+Yr>5%u7?G!dCqL`Z(&Gn(XZsjb;{ncr0E%vMSz)iQk+SBcHIN}0RrbuEfV{#<1-(1+UgVaq7 zH{mmS39Y)nS<4&YPfqZ8X$q1OiA?;&1APKMtvU5Yv3EGVkA&jEZ~A|4v(%NzH&e9z zTxs)3R_-DmB9OK{SgIl@KpN4wNq|NUEhCw*i*FuHrH@ONiS@XPmU!iZJ{X+yPJff? z^D2f$q9Pyv(5M!qmS8c<&}n6FvIn173->7U2jejQ+j>PDQs;L8H}-R+*wr0;c)37J zhMZI}*Y*6oOw=cQ(NA(OSgbEFOdZai5bu*Bh0KaPuV46q7p3OEz}Bvm5_09g38&h- z{ky#?Ak`}`fSrS{vRWr_xPo=zm2?l}Zw?;;B(k&+FxfRQ67?D7MVW)_rt-3aAVQ|2 z9CZtGM?Ywn{PK_k_LCMlj{b#ZV#z?|54e>AugD8f7W9-V`#ITD&JD zN+aTD4Sz%xdoxV>N@vR zU^-P#`m5U_9%B2Z(dwB{*>o2fMtO^Yt-CGFH$R&nj3Ru)D@9yRp+UXw=BM3uo(h%& zCq8}W{{O=C%0W81Dp2jlPYS8Omk95hxyr(YjppOzmuctTw{B3fn}E8B;xNeO273Gv z5EK+9DR=`WLyOIUEs6gZTNeK}wp^tp|5g`JRTi73tL=`bJ~6%4b8vzF!jNX<)XqQ{ zxAAnla4M)7L_x}ph2NGtV@tRGYZ!$SRuFW!gZR@?jb>IEPIqN2IdXFq1J3@leSU($ zdsa6(OjqzB*~Fj-!{^-gym0BOZl)Q;VF`$k2!-%H63_iaBy+9yV+VE4V5kGVX#$zZ zXxjt#UiT0WOlHefZQge!pR@a_^`|F{q6{jE@4;OJ{sRs?!lc>fFGCl@z7$YFp%2|K z<0z+F3r^Fd0`*ad$z*aSnAs=Jf8Nf5DSuyyQJS%>a}e}VRAe`qu`wBpaK}l1+RpN% z9kkbK{1wZ(lf^#%;B4et;$<*wsY;4*JSWEzS{6smll0@lw{Nf#H2_aAMNEg1iobvktaI#)pcE^{p-5+x~Pvyv&_tE5Iw0P%QR-9XX z-!siBwlm#$0>s0aJ*yaR4BN=);|{@IaW^NAVC1il--=%lJidjt>i>}3>~(=>f+CgI zYo9uPT@0;~_xQ^7#ddRxV>9HqD*1|cTuOC!flk;mM#9o}h&t?mr$DXa%daNt)FB+c z72mGrBlfNI@ZYU^8YeQJ-jO23$Q9Y(C+%OA8Lb@Gx8hN52Zb-1-~7WhP3(A?^rsYvR}eA3P>e*`d|=_gAQ7dD;-doZYH{@d*dujT9pMTR!>b` z5(Gq&VtLCV;@F1Bxx)lC{_-rVf8(g5mf4A2U~xBoBxiA53e)!b#Mo2r%4YK@Q&^0f z@z+_m*me4883uQS)~*(GuM(#fCQI*gH?PJOQ9*PXSF)ojq`fUHy|cr+WvfCJ1= zCJpRO(}h`K;i7Lehfy_f+g6G+&_x*hw~ALYE)vBN1a#JIs_;tp}>cLyZfdwom5MGQCFNs4f&XRFcALRk5;YexS}uHmzz`=>5~zx)HkKq;(&=g{9U`y-h#0N5RPiL6=~k+b9)U5PRU~Q{H&Z+ zle?@`!Yn40%E{q3akXzVKY!pjyGs@5_M3~~ycTObo9;{Ftv`LNBP8b@Vv<)EC`cpI zFWh2B>$dCEXt@fa?-@xQc$vT8{-QTqBm5P``wBK^X#pY3pWo%o_bpnX0hAs#2jjB0 zEXX|VZn|MXZqv_!T=+I|JlQZ_dCUpYS~VMqea#7__KI=98MK+Q+R-!BMGO_Za->a4-$Kmei52#B__gHWd~xKf&OS$HCwRvae}0(Fw3h44*Sw-1 z@RnZW)v&o=d5QQm^pK}P{AoNy23CD3K9>g$^}`@fc=t_8R?h;WVXlM62k_7ExV4S^ zrd_?RroXTQ-vTSB8L5%wd%JfQN9iU!Jft%Ythpuveu&YN=tfBW2t8}i=Z=zU)LEAA zmfNb4Qi{57%!Nt#obW4FI!*d}7w&+^Ml_)-7eW2d#`wmU1*@VdLX?*d?y2knJCLJheM^BRa7F>nm zVAlGMyF1>&dc08kmp~}h@-5>b5RxCT0`SIOhct`t{87B)Bgtgc;&xT(>Beh{7AH` z8?VnrZijxjw+`(aVePH3jGEbG`I?s&HH^;0iD!b@-|svvn*#X#@w^pG_ESC@5)}LH zdDsQUCNvx2+HzbM`A^R`Vz0M6W$&;V=BcXG(n0m(Oc+=yq^tn%VnofRKkxiW|ZXQBE_(9Q7?{X}hP zG94VFa+4bUtQ`Tr7l*55Y%iAzrJu6r*|TjlH!lix)CmD%>p(oOIrUPwwnEA3e!!B; z{5}_fgLuV6(Ni|%(mP`Ge%3TS=7sO3j;~TfpKXa5K4Ym=wVi6s41DIn1^eA+VBupR zk}tv*4kKYCpRISZgZV;iTJ6|utGi4wE(0diSFwaoNXkC$YXSpSyn{5BVOnFdl$^ia z71`GH7W7nq3^GQD7mWnWZs$FT&_i3DJ>a7{D>ue@8Zv=XP;#0irhla|8dK$XRgqkj6 zhGS$Q?Vz`ujG7|dULnoVikP=kDT*N@N@s-NP+-z%oQd&!B|hnrx6&Z;p-qsHPdrPo z)-Z0SMhTZNrD4E`Bazl57axi5BrNfmC4P48CSRq>+#TEe7D|ep*Yy(7TOw4rk1M=W zp^uY=Y<ejV+ez=o>0gA*X3`yrd`F=HqTRMjx{ArpZd} zU+Z}P^=cp7e+sZJf64luKW(JDFplIxedqud^<4_ns9&HzvxV~?!nHy5uxBy2v6;hZDxc!kf=?5QYDKX z6YU=jd2Q>^TfkA?q^MkE*0uE?Drx-7f{ZWU3w7b9FH<*r?L^Y0pmqi(pnnPrj05vH z1Fwc$|5QI>RdsE$WC9u!;2&+*;yCO&5dqAYMi32Ud4?{>uiKepw-!9ND`V8H8T8c~icVwL3JJR%?pP#B+iZ^4bM*@29 zu*E0b&PP>yI=oj#0&-CICbu;Nm7{=}>F9F>*R8z{O2!N~!?-`23*n-jhszhkhJ+pz zK4zYwLV)m$w@Me`3ze@biAQ04j`_djh+sqsDgWL?&%2p{2lzj z1FGLx>-101)xp-ka$4Nr?TA<+k^kjfvCNA{CUT*K_kw@4JAIVrg4x`9lTjI7@heiF z^h7BHZ}{A=zUCKiJh!A+nI3q&YpkXgxtn>`CVr*z?)vt$Y@Y~ zD45qe>|u2Zt*-4K%R-!(hblL^*e?^R#RzNew^;wCUCDY<^o2vLp5$!KEw!bm(=~Ni z%v1bQZSzAiQsg;NcI7R%7Q5d={;pbsDnkniHXAyLB51_9DtAc`p3+sTFyY(HQNbLe?kGb8l z?ZnFx3xAXh^@b6;kPVXVCepxYpYv{8yTfvF8Om&|cG>=}*-dP_54T;7&M%=!TD~$$ zma~MgYGHA(l{efL_C=DVYp|Ue%O&5ETZI36E`6|&UI&(EkM*qE8?N_0lBBf&Z4M}~ zV=Gx{l#Z;zZ~o8k*9GXh_~ji%Oj2o6zd`4B*ur)}CJK0!?d;zWLdQ$Q+iNnW>qj~d zUiYxg)fpJDWX}2isASD;o9356t?8cu+mv;^RrR&qc7f<0?EPt>TFkeYxrzxnimRP} z=Uxu5N#|E&BE~iAAN+K4@bwlDm_RwGc|p~L?g$@ z5DSa64De555~MchyGPH~ZOU&qVSU}r=rgm~^;!1E9-!9zL!X1VAP=+il~r=M){9+C z5RtqG-!T`s8`w(-fnrks7XnhceM)_nTR(c00aVTf1kq zUPiTJ?Fkk%L`f@R&tG)9h)bK&c;?n{6;u#j#VM*U(+1#X!=btg9X)uaYETycz(qu! zP+**LoB_z(rrLcsOYU+-Qx3VPFa@2{5%4gP>JMgx-Y;s#(0^;+lTOSlbHq~xpH-oI zuAE{dq*j3j*tE`lb%mS%)SY7oxG$c&^j{H}q9#3a@HY-?-|Ee>?yJyNwk`?Fhak@_ zp$fm4Br&O)M*5z3NXzCx5Kua=_AsT$0YRvz_s$n>(J}tXg21@iAOL-ylsv`73Y86~ z4R*gWxbO0=T2jG}F)CyFWe%=Oto)iZi>&&LQbKe+pe0e&lj*T5wPPpo9ITwHJc4S8 zo1V5uLtD?%zbi9ncA)a91Fx7G;&;EdS#g2Mc6|?QjOFNe_^s$Ed8kPDynu+a%1^Bj}Mg@Pd$C(++8CA*1W7pE+Jx896?BYoGJGZ2t>U1iO3P+Fg2b z9s7E(U*=!NbhX!Z31%xG)c4%A*N?1t1YL|~QV1LAy|)9%%_Cm$o#GW;djmNYnucI^ zz1igJL@?wwNBIk((~&w;Q1FAa>9GW3LTc|*HCA&Lei4eq>CdR#ZUggS3uW2QYng$G z@zNQG5*kOWGsW;IY$`X9bhOwi@~y3zDeu!g#DvJk1DX#!-D1|bsFw^^MkajbL=aO| z#Sh+@OvFM|3zm??gS>O0Ce3oNh|$rrZE1Cd#S`XmF32#72Fj{@d&)G!G#+a`+3#4z z)XEHjheNCjFjYn7Mi~h3l>QP2{lp0UxKun_PT-&)>0tC(+4(D>q{c2wHuNJ}Mbmvt z1YLhG#ZcPbfTfT00UgRAUw8+(LW$P)wy!6X{0F&vO6=}u$KmUj#TPQn2W1C?fmtZ~ zsoor%d$y9Z97i9=ck_=kZV6T*(%9-g5T94;ReShOl<0Anc{4Y^C)|T+SZ*(ruHQ;Dr2pXpNSKWN6!^`{ z_R?jpmNC+(hIT9*OnIyYgiXmhFBYtzq7JAPh&M*c8l5X$94CIxXmR&v8r+nLHo=cH zS0HgQ^#y{y(c+_{!!q(?>!x>nfDlwc+Ln&IOkRK^y+dWF!E>GTamctmBVc;Kf^bPI- z!aKzL+?{{;C{?x1+Im@())4xY4G^EmAH^~3d84@h`g#dnO@ zZB+!7iX&6fg6+)j-!qbcne-v)ZWL`c zI|;{T-rI2i<#JFY6@Y~==ReWZ97pJadI~IS&>T=Cor7`!qz?kGZ{tT`Vp+30P_;g; z@THE2u!m4vo(uMGZ?UbPZ}sj!a=+;QM*Ty|qTthvmw~}LjND`Kb>V94*5bIThg?J) zA--`%_f2IUKLYF1uU?zPTXB=ck>y19E-|^yx9ZNhrQ*?3FxXXuH0|G*I8K4TN;hv{ zA~?$1x!90Jz)!gulKBeGkTqKbI6^2cdP_2*N7Eyadd~ZWxUP%o$^HVoT#=^UlPN`b z66R@_i5v?&1}&K}Q7$Ulfdk zPo5qZ-MdG=Jh!ql(kB3u>bL({pnd^X)aTPcXqkqboLi>6ew5VvG_>rI^!JMV6N)z# zTLtdg3Jt^p?{9vUpYCQRu~d~=fsvAB6)e0zx=}(;g~beSVD%q0ZEqfaB3zJviR=Ho zEr}NpzkdwDF${R)6_OtSch3Rq;{a^}J>WLl3LT#JbtpmBRNd;bN=EzdVUFlPQE9E# z675%HRW9uE*#}xwkms>+O~iS$fKxjmUq)x3i)fuG|93>_$w}hG87A2bY90wQDKi0f zH!aSwRqVg@?tr82BRAYGbmp$~mXQ-ZK@+drj0*v3QWw9`9fO!W(`;Vso)RB}*RI+} zFJ$Fxc3szsy%kJ}y>HN+|0)*^vAaJ&yB|3*#%M6V9ad2or?*#jFj;Z$bCd_V*3Z+F z5(8TbFclZT0?9)Zq&nVouWKl4{yE1*^{MI(+&B02E$3E$>Ifi~xp-v^jA3$q0l=X;Ah|ZXJHgx*T?B1wGT?cYRHaxw3$0A^H_0*K)PW&LwaKeH^uB|wi;2F)UlASf;)-m zuUrqtnate`gpKYrg@Il+vBy5e?{8f!TgoyE=fJ-WQMLc(APVy6ME3(?Bl%dl^5q{~ z6zz8PJa?&c{@b7PZ5K>U%^txSj@=MR1xCV}EpjsAY*n?yjNajewpz{_6Tl%`vF^i;Is#T3}fPXOKSGw9yFjP&OMd)r`Y_$NLlq$w~ta@Ro@kQvAzK=9UU9jgrBc?%F5{W7L?7|9$8P9f-ZL13Zc;WP)eC-I8U_nH z0)I;r$~Yb#rzJ!oJn5GJFHzH5;6(_k>=cj=`d6b~9&h>pjujlaHE=4dltmcN0fHcG zZV>Q1?`>E5d>UX;tSwZ+Dvq~)`=cH`SiFfGiNjdnNwCs?gN-4kjJRc|Q;BsM#kq(T zE@qh?N+|a;9bDqjE0mHVl<5u6R8)P>0Y$;EajwP*><8t>=(Qf?cGl}j!Ni25F33_n z;&L}o3}S8 zigDJS>7-F$@?V)`rKu8%p5aLZ*MN0bfd9#-dI!fzwS~o^m_qcDz7d&g!`)$JKdb`c zk@}dVA>fe^0Pe>BLk#{4WygRxXfr;DhkiK%9d8^gbU$|p3ORrbNvRP%JB+y)5M!P& zc~}k9YbPt3X6)ghSYDSLu+U%*!=pvY|8r2r8Aak+;BE@x_%;U?7syZldJbt;nklr; zc&8{Vls0>KHWv058zo?i#Fuu%Wu@#re}7bxFt%7SRx{d8OAidOU}&lqa@NfCfpqb{ z1HUaU088rh_pSdd9D$y-7bcUkNdgFiO+N zxCoKLf48U-#@%sag|wO#S3GxJJ{kD_>rK3A(eQtsRPC}zSNifsaff*DW = ({ schema, required, name }) => { - if (schema.type === "object" || schema.properties) { - return ( - - - {schema.title || name} - - - object - - - {schema.properties && Object.entries(schema.properties).map(([n, prop]: [string, JSONSchema4], i: number) => { - return ( - - ); - })} - - - ); - } if (schema.anyOf) { return ( @@ -89,7 +63,7 @@ const SchemaRenderer: React.FC = ({ schema, required, name }) => { ); } - if (schema.type === "array" && schema.items instanceof Array) { + if (schema.items instanceof Array) { return ( @@ -104,7 +78,7 @@ const SchemaRenderer: React.FC = ({ schema, required, name }) => { ); } - if (schema.type === "array" && schema.items) { + if (schema.items) { return ( @@ -119,14 +93,54 @@ const SchemaRenderer: React.FC = ({ schema, required, name }) => { ); } + + 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.type} {schema.pattern} - {required ? "required" : "optional"} + {required ? "yes" : ""} {schema.description} ); From e2fb491197bdefac0be0b643edc6f1a45a9a2242 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Wed, 15 Jan 2020 18:01:34 -0800 Subject: [PATCH 3/3] fix: true/false display --- src/JSONSchema/SchemaRenderer.tsx | 2 +- src/JSONSchema/fields/JSONSchemaFields.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JSONSchema/SchemaRenderer.tsx b/src/JSONSchema/SchemaRenderer.tsx index 9fba337..da60b84 100644 --- a/src/JSONSchema/SchemaRenderer.tsx +++ b/src/JSONSchema/SchemaRenderer.tsx @@ -140,7 +140,7 @@ const SchemaRenderer: React.FC = ({ schema, required, name }) => { color: colorMap[schema.type as any], }}>{schema.type} {schema.pattern} - {required ? "yes" : ""} + {required ? "true" : "false"} {schema.description} ); diff --git a/src/JSONSchema/fields/JSONSchemaFields.test.tsx b/src/JSONSchema/fields/JSONSchemaFields.test.tsx index aef3596..1bfb298 100644 --- a/src/JSONSchema/fields/JSONSchemaFields.test.tsx +++ b/src/JSONSchema/fields/JSONSchemaFields.test.tsx @@ -54,7 +54,7 @@ it("renders with a schema required", () => { ], } as JSONSchema4; ReactDOM.render(, div); - expect(div.innerHTML.includes("required")).toBe(true); + expect(div.innerHTML.includes("true")).toBe(true); ReactDOM.unmountComponentAtNode(div); }); @@ -69,7 +69,7 @@ it("renders with a schema without required", () => { }, } as JSONSchema4; ReactDOM.render(, div); - expect(div.innerHTML.includes("optional")).toBe(true); + expect(div.innerHTML.includes("false")).toBe(true); ReactDOM.unmountComponentAtNode(div); });