From bcfef33a7f400ed91ced16a1682d4b843f7ba1c1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 12:40:30 +0200 Subject: [PATCH] config-schema: break out schema browser and scroll targets context Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 2 +- .../SchemaBrowser/SchemaBrowser.tsx | 196 ++++++++++++++++++ .../src/components/SchemaBrowser/index.ts | 17 ++ .../src/components/SchemaView/ChildView.tsx | 2 +- .../components/SchemaViewer/SchemaViewer.tsx | 183 +--------------- .../ScrollTargetsContext.tsx | 0 .../components/ScrollTargetsContext/index.ts | 20 ++ 7 files changed, 239 insertions(+), 181 deletions(-) create mode 100644 plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx create mode 100644 plugins/config-schema/src/components/SchemaBrowser/index.ts rename plugins/config-schema/src/components/{SchemaViewer => ScrollTargetsContext}/ScrollTargetsContext.tsx (100%) create mode 100644 plugins/config-schema/src/components/ScrollTargetsContext/index.ts diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index add916e997..4bc7cfd98c 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -27,7 +27,7 @@ export const ConfigSchemaPage = () => { return ( -
+
{schema ? : 'No schema available'} diff --git a/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx b/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx new file mode 100644 index 0000000000..52005802b8 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createStyles, fade, withStyles } from '@material-ui/core'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; +import { Schema } from 'jsonschema'; +import React, { ReactNode, useMemo, useRef } from 'react'; +import { useScrollTargets } from '../ScrollTargetsContext'; + +const StyledTreeItem = withStyles(theme => + createStyles({ + label: { + userSelect: 'none', + }, + group: { + marginLeft: 7, + paddingLeft: theme.spacing(1), + borderLeft: `1px solid ${fade(theme.palette.text.primary, 0.15)}`, + }, + }), +)((props: TreeItemProps) => ); + +export function createSchemaBrowserItems( + expanded: string[], + schema: Schema, + path: string = '', + depth: number = 0, +): ReactNode { + let matchArr; + if (schema.anyOf) { + matchArr = schema.anyOf; + } else if (schema.oneOf) { + matchArr = schema.oneOf; + } else if (schema.allOf) { + matchArr = schema.allOf; + } + if (matchArr) { + return matchArr.map((childSchema, index) => { + const childPath = `${path}/${index + 1}`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }); + } + + switch (schema.type) { + case 'array': { + const childPath = `${path}[]`; + if (depth > 0) expanded.push(childPath); + return ( + + {schema.items && + createSchemaBrowserItems( + expanded, + schema.items as Schema, + childPath, + depth + 1, + )} + + ); + } + case 'object': + case undefined: { + const children = []; + + if (schema.properties) { + children.push( + ...Object.entries(schema.properties).map(([name, childSchema]) => { + const childPath = path ? `${path}.${name}` : name; + if (depth > 0) expanded.push(childPath); + return ( + + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }), + ); + } + + if (schema.patternProperties) { + children.push( + ...Object.entries(schema.patternProperties).map( + ([name, childSchema]) => { + const childPath = `${path}.<${name}>`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }, + ), + ); + } + + if (schema.additionalProperties && schema.additionalProperties !== true) { + const childPath = `${path}.*`; + if (depth > 0) expanded.push(childPath); + children.push( + + {createSchemaBrowserItems( + expanded, + schema.additionalProperties, + childPath, + depth + 1, + )} + , + ); + } + + return <>{children}; + } + + default: + return null; + } +} + +export function SchemaBrowser({ schema }: { schema: Schema }) { + const scroll = useScrollTargets(); + const expandedRef = useRef([]); + const data = useMemo(() => { + const expanded = new Array(); + + const items = createSchemaBrowserItems(expanded, schema); + + return { items, expanded }; + }, [schema]); + + if (!scroll) { + throw new Error('No scroll handler available'); + } + + const handleToggle = (_event: unknown, expanded: string[]) => { + expandedRef.current = expanded; + }; + + const handleSelect = (_event: unknown, nodeId: string) => { + if (expandedRef.current.includes(nodeId)) { + scroll.scrollTo(nodeId); + } + }; + + return ( + } + defaultExpandIcon={} + onNodeToggle={handleToggle} + onNodeSelect={handleSelect} + > + {data.items} + + ); +} diff --git a/plugins/config-schema/src/components/SchemaBrowser/index.ts b/plugins/config-schema/src/components/SchemaBrowser/index.ts new file mode 100644 index 0000000000..2b3e8fe79a --- /dev/null +++ b/plugins/config-schema/src/components/SchemaBrowser/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SchemaBrowser } from './SchemaBrowser'; diff --git a/plugins/config-schema/src/components/SchemaView/ChildView.tsx b/plugins/config-schema/src/components/SchemaView/ChildView.tsx index bc4f3bb63e..ff970d5251 100644 --- a/plugins/config-schema/src/components/SchemaView/ChildView.tsx +++ b/plugins/config-schema/src/components/SchemaView/ChildView.tsx @@ -18,7 +18,7 @@ import { JsonValue } from '@backstage/config'; import { Box, Chip, Divider, makeStyles, Typography } from '@material-ui/core'; import { Schema } from 'jsonschema'; import React, { useEffect, useRef } from 'react'; -import { useScrollTargets } from '../SchemaViewer/ScrollContext'; +import { useScrollTargets } from '../ScrollTargetsContext/ScrollTargetsContext'; import { SchemaView } from './SchemaView'; export interface MetadataViewRowProps { diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 3ad1f55451..3ed3cca0ee 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -14,192 +14,17 @@ * limitations under the License. */ -import { Box, createStyles, fade, Paper, withStyles } from '@material-ui/core'; -import ChevronRightIcon from '@material-ui/icons/ChevronRight'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; +import { Box, Paper } from '@material-ui/core'; import { Schema } from 'jsonschema'; -import React, { ReactNode, useMemo, useRef } from 'react'; +import React from 'react'; import { SchemaView } from '../SchemaView'; -import { ScrollTargetsProvider, useScrollTargets } from './ScrollContext'; +import { SchemaBrowser } from '../SchemaBrowser'; +import { ScrollTargetsProvider } from '../ScrollTargetsContext/ScrollTargetsContext'; export interface SchemaViewerProps { schema: Schema; } -const StyledTreeItem = withStyles(theme => - createStyles({ - label: { - userSelect: 'none', - }, - group: { - marginLeft: 7, - paddingLeft: theme.spacing(1), - borderLeft: `1px solid ${fade(theme.palette.text.primary, 0.15)}`, - }, - }), -)((props: TreeItemProps) => ); - -export function createSchemaBrowserItems( - expanded: string[], - schema: Schema, - path: string = '', - depth: number = 0, -): ReactNode { - let matchArr; - if (schema.anyOf) { - matchArr = schema.anyOf; - } else if (schema.oneOf) { - matchArr = schema.oneOf; - } else if (schema.allOf) { - matchArr = schema.allOf; - } - if (matchArr) { - return matchArr.map((childSchema, index) => { - const childPath = `${path}/${index + 1}`; - if (depth > 0) expanded.push(childPath); - return ( - `} - > - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }); - } - - switch (schema.type) { - case 'array': { - const childPath = `${path}[]`; - if (depth > 0) expanded.push(childPath); - return ( - - {schema.items && - createSchemaBrowserItems( - expanded, - schema.items as Schema, - childPath, - depth + 1, - )} - - ); - } - case 'object': - case undefined: { - const children = []; - - if (schema.properties) { - children.push( - ...Object.entries(schema.properties).map(([name, childSchema]) => { - const childPath = path ? `${path}.${name}` : name; - if (depth > 0) expanded.push(childPath); - return ( - - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }), - ); - } - - if (schema.patternProperties) { - children.push( - ...Object.entries(schema.patternProperties).map( - ([name, childSchema]) => { - const childPath = `${path}.<${name}>`; - if (depth > 0) expanded.push(childPath); - return ( - `} - > - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }, - ), - ); - } - - if (schema.additionalProperties && schema.additionalProperties !== true) { - const childPath = `${path}.*`; - if (depth > 0) expanded.push(childPath); - children.push( - - {createSchemaBrowserItems( - expanded, - schema.additionalProperties, - childPath, - depth + 1, - )} - , - ); - } - - return <>{children}; - } - - default: - return null; - } -} - -export function SchemaBrowser({ schema }: { schema: Schema }) { - const scroll = useScrollTargets(); - const expandedRef = useRef([]); - const data = useMemo(() => { - const expanded = new Array(); - - const items = createSchemaBrowserItems(expanded, schema); - - return { items, expanded }; - }, [schema]); - - if (!scroll) { - throw new Error('No scroll handler available'); - } - - const handleToggle = (_event: unknown, expanded: string[]) => { - expandedRef.current = expanded; - }; - - const handleSelect = (_event: unknown, nodeId: string) => { - if (expandedRef.current.includes(nodeId)) { - scroll.scrollTo(nodeId); - } - }; - - return ( - } - defaultExpandIcon={} - onNodeToggle={handleToggle} - onNodeSelect={handleSelect} - > - {data.items} - - ); -} - export const SchemaViewer = ({ schema }: SchemaViewerProps) => { return ( diff --git a/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx b/plugins/config-schema/src/components/ScrollTargetsContext/ScrollTargetsContext.tsx similarity index 100% rename from plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx rename to plugins/config-schema/src/components/ScrollTargetsContext/ScrollTargetsContext.tsx diff --git a/plugins/config-schema/src/components/ScrollTargetsContext/index.ts b/plugins/config-schema/src/components/ScrollTargetsContext/index.ts new file mode 100644 index 0000000000..d2d35ec6b1 --- /dev/null +++ b/plugins/config-schema/src/components/ScrollTargetsContext/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + ScrollTargetsProvider, + useScrollTargets, +} from './ScrollTargetsContext';