Skip to content

Commit b0eff89

Browse files
committed
feat: masking [STU-18] (#12)
* feat: masking * test: more tests * style: edit mask btn
1 parent 4815780 commit b0eff89

File tree

14 files changed

+387
-53
lines changed

14 files changed

+387
-53
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"lodash": "4.17.x"
4848
},
4949
"devDependencies": {
50+
"@sambego/storybook-state": "^1.3.4",
5051
"@stoplight/scripts": "4.1.0",
5152
"@stoplight/storybook-config": "1.4.0",
5253
"@stoplight/types": "4.1.0",

src/SchemaView.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { Dictionary, Omit } from '@stoplight/types';
22
import { Box, Button, IBox } from '@stoplight/ui-kit';
33
import { JSONSchema4 } from 'json-schema';
4+
import _get = require('lodash/get');
45
import * as React from 'react';
56
import { MutedText } from './components/common/MutedText';
7+
import { MaskedSchema } from './components/MaskedSchema';
68
import { IProperty, Property } from './components/Property';
79
import { TopBar } from './components/TopBar';
810
import { useMetadata } from './hooks/useMetadata';
911
import { useProperties } from './hooks/useProperties';
1012
import { useTheme } from './theme';
13+
import { IMasking } from './types';
1114
import { isExpanded } from './utils/isExpanded';
1215
import { pathToString } from './utils/pathToString';
1316

14-
export interface ISchemaView extends Omit<IBox, 'onSelect'> {
17+
export interface ISchemaView extends Omit<IBox, 'onSelect'>, IMasking {
1518
name?: string;
1619
defaultExpandedDepth?: number;
1720
dereferencedSchema?: JSONSchema4;
@@ -29,17 +32,22 @@ export const SchemaView: React.FunctionComponent<ISchemaView> = props => {
2932
limitPropertyCount,
3033
schema,
3134
dereferencedSchema,
35+
selected,
36+
canSelect,
37+
onSelect,
3238
name,
3339
...rest
3440
} = props;
3541

3642
const theme = useTheme();
3743
const [showExtra, setShowExtra] = React.useState<boolean>(false);
44+
const [maskedSchema, setMaskedSchema] = React.useState<JSONSchema4 | null>(null);
3845
const [expandedRows, setExpandedRows] = React.useState<Dictionary<boolean>>({ all: expanded });
3946
const { properties, isOverflow } = useProperties(schema, dereferencedSchema, {
4047
expandedRows,
4148
defaultExpandedDepth,
4249
...(!showExtra && { limitPropertyCount }),
50+
...(canSelect && selected && { selected }),
4351
});
4452
const metadata = useMetadata(schema);
4553

@@ -62,15 +70,34 @@ export const SchemaView: React.FunctionComponent<ISchemaView> = props => {
6270
[showExtra]
6371
);
6472

73+
const handleMaskEdit = React.useCallback<IProperty['onMaskEdit']>(node => {
74+
setMaskedSchema(_get(dereferencedSchema, node.path));
75+
}, []);
76+
77+
const handleMaskedSchemaClose = React.useCallback(() => {
78+
setMaskedSchema(null);
79+
}, []);
80+
6581
if (properties.length === 0) {
6682
return <MutedText>{emptyText}</MutedText>;
6783
}
6884

6985
return (
7086
<Box backgroundColor={theme.canvas.bg} color={theme.canvas.fg} {...rest}>
87+
{maskedSchema && (
88+
<MaskedSchema onClose={handleMaskedSchemaClose} onSelect={onSelect} selected={selected} schema={maskedSchema} />
89+
)}
7190
<TopBar name={name} metadata={metadata} />
7291
{properties.map((node, i) => (
73-
<Property key={i} node={node} onClick={toggleExpandRow} />
92+
<Property
93+
key={i}
94+
node={node}
95+
onClick={toggleExpandRow}
96+
onSelect={onSelect}
97+
onMaskEdit={handleMaskEdit}
98+
selected={selected}
99+
canSelect={canSelect}
100+
/>
74101
))}
75102
{showExtra || isOverflow ? (
76103
<Button onClick={toggleShowExtra}>{showExtra ? 'collapse' : `...show more properties`}</Button>

src/__mocks__/theme.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
11
export const useTheme = () => ({
2+
base: 'dark',
3+
24
canvas: {
35
bg: '#111',
46
fg: '#fff',
57
error: 'red',
6-
muted: 'rgba(255, 255, 255, 0.4)',
8+
muted: 'rgba(255, 255, 255, 0.6)',
79
},
810

911
divider: {
1012
bg: '#bababa',
1113
},
1214

1315
row: {
14-
hoverBg: '#999',
15-
hoverFg: '#222',
16-
evenBg: '#333',
16+
hoverBg: '#333',
17+
hoverFg: '#fff',
18+
evenBg: '#232222',
19+
},
20+
21+
types: {
22+
object: '#83c1ff',
23+
array: '#7dff75',
24+
allOf: '#b89826',
25+
oneOf: '#b89826',
26+
anyOf: '#b89826',
27+
null: '#ff7f50',
28+
integer: '#e03b36',
29+
number: '#e03b36',
30+
boolean: '#ff69b4',
31+
binary: '#8ccda3',
32+
string: '#19c5a0',
33+
$ref: '#a359e2',
1734
},
1835
});
1936

src/__stories__/JsonSchemaViewer.tsx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import * as React from 'react';
22

3+
import { State, Store } from '@sambego/storybook-state';
34
import { boolean, number, object, text, withKnobs } from '@storybook/addon-knobs';
45
import { storiesOf } from '@storybook/react';
56

6-
import { JsonSchemaViewer } from '../JsonSchemaViewer';
77
import * as schema from '../__fixtures__/default-schema.json';
8+
import { JsonSchemaViewer } from '../JsonSchemaViewer';
89

9-
import * as schemaWithRefs from '../__fixtures__/ref/original.json';
10-
import * as dereferencedSchema from '../__fixtures__/ref/resolved.json';
10+
import * as schemaWithRefs from './__fixtures__/ref/original.json';
11+
import * as dereferencedSchema from './__fixtures__/ref/resolved.json';
1112

1213
storiesOf('JsonSchemaViewer', module)
1314
.addDecorator(withKnobs)
@@ -20,16 +21,36 @@ storiesOf('JsonSchemaViewer', module)
2021
expanded={boolean('expanded', true)}
2122
/>
2223
))
23-
.add('with dereferenced schema', () => (
24-
<JsonSchemaViewer
25-
name={text('name', 'my schema')}
26-
schema={schemaWithRefs}
27-
dereferencedSchema={dereferencedSchema}
28-
limitPropertyCount={number('limitPropertyCount', 20)}
29-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
30-
expanded={boolean('expanded', true)}
31-
/>
32-
))
24+
.add('with dereferenced schema', () => {
25+
const store = new Store<{ selected: string[] }>({
26+
selected: [],
27+
});
28+
29+
return (
30+
<State store={store}>
31+
<JsonSchemaViewer
32+
name={text('name', 'name')}
33+
schema={schemaWithRefs}
34+
dereferencedSchema={dereferencedSchema}
35+
limitPropertyCount={number('limitPropertyCount', 20)}
36+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
37+
onSelect={(path: string) => {
38+
const selected = [...store.get('selected')];
39+
const index = selected.indexOf(path);
40+
if (index !== -1) {
41+
selected.splice(index, 1);
42+
} else {
43+
selected.push(path);
44+
}
45+
46+
store.set({ selected });
47+
}}
48+
selected={store.get('selected')}
49+
expanded={boolean('expanded', true)}
50+
/>
51+
</State>
52+
);
53+
})
3354
.add('custom schema', () => (
3455
<JsonSchemaViewer
3556
name={text('name', 'my schema')}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"address": {
5+
"type": "object",
6+
"properties": {
7+
"street_address": {
8+
"type": "string"
9+
},
10+
"city": {
11+
"type": "string"
12+
},
13+
"state": {
14+
"type": "string"
15+
}
16+
},
17+
"required": [
18+
"street_address",
19+
"city",
20+
"state"
21+
]
22+
}
23+
},
24+
"type": "object",
25+
"properties": {
26+
"billing_address": {
27+
"$ref": "#/definitions/address"
28+
},
29+
"shipping_address": {
30+
"$ref": "#/definitions/address"
31+
}
32+
}
33+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"address": {
5+
"type": "object",
6+
"properties": {
7+
"street_address": {
8+
"type": "string"
9+
},
10+
"city": {
11+
"type": "string"
12+
},
13+
"state": {
14+
"type": "string"
15+
}
16+
},
17+
"required": [
18+
"street_address",
19+
"city",
20+
"state"
21+
]
22+
}
23+
},
24+
"type": "object",
25+
"properties": {
26+
"billing_address": {
27+
"type": "object",
28+
"properties": {
29+
"street_address": {
30+
"type": "string"
31+
},
32+
"city": {
33+
"type": "string"
34+
},
35+
"state": {
36+
"type": "string"
37+
}
38+
},
39+
"required": [
40+
"street_address",
41+
"city",
42+
"state"
43+
]
44+
},
45+
"shipping_address": {
46+
"type": "object",
47+
"properties": {
48+
"street_address": {
49+
"type": "string"
50+
},
51+
"city": {
52+
"type": "string"
53+
},
54+
"state": {
55+
"type": "string"
56+
}
57+
},
58+
"required": [
59+
"street_address",
60+
"city",
61+
"state"
62+
]
63+
}
64+
}
65+
}

src/components/Additional.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export const Additional: React.FunctionComponent<Pick<SchemaTreeNode, 'additiona
1616

1717
const content = React.useMemo(() => safeStringify(additional, undefined, 2), [additional]);
1818

19-
if (typeof content !== 'string') return trigger;
20-
2119
return (
2220
<Popup
2321
posX="left"

src/components/MaskedSchema.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Dialog } from '@stoplight/ui-kit';
2+
import * as React from 'react';
3+
import { IJsonSchemaViewer, JsonSchemaViewer } from '../JsonSchemaViewer';
4+
5+
export interface IMaskedSchema extends IJsonSchemaViewer {
6+
onClose(): void;
7+
}
8+
9+
export const MaskedSchema: React.FunctionComponent<IMaskedSchema> = ({ onClose, ...props }) => {
10+
return (
11+
<Dialog show onClickOutside={onClose}>
12+
<JsonSchemaViewer {...props} canSelect />
13+
</Dialog>
14+
);
15+
};

0 commit comments

Comments
 (0)