@jupyterlab/json-extension 4.0.0-alpha.9 → 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/component.d.ts +2 -0
- package/lib/component.js +26 -23
- package/lib/component.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -8
- package/lib/index.js.map +1 -1
- package/package.json +22 -18
- package/src/component.tsx +209 -0
- package/src/index.tsx +112 -0
- package/style/base.css +5 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/lib/component.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface IProps {
|
|
|
11
11
|
* The application language translator.
|
|
12
12
|
*/
|
|
13
13
|
translator?: ITranslator;
|
|
14
|
+
forwardedRef?: React.Ref<HTMLDivElement>;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* The state of the JSON tree component.
|
|
@@ -28,6 +29,7 @@ export declare class Component extends React.Component<IProps, IState> {
|
|
|
28
29
|
value: string;
|
|
29
30
|
};
|
|
30
31
|
timer: number;
|
|
32
|
+
componentDidMount(): void;
|
|
31
33
|
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
32
34
|
render(): JSX.Element;
|
|
33
35
|
}
|
package/lib/component.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
import { jupyterHighlightStyle } from '@jupyterlab/codemirror';
|
|
3
4
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
4
5
|
import { InputGroup } from '@jupyterlab/ui-components';
|
|
6
|
+
import { tags } from '@lezer/highlight';
|
|
5
7
|
import { JSONExt } from '@lumino/coreutils';
|
|
6
8
|
import * as React from 'react';
|
|
7
|
-
import
|
|
9
|
+
import Highlighter from 'react-highlight-words';
|
|
8
10
|
import { JSONTree } from 'react-json-tree';
|
|
11
|
+
import { StyleModule } from 'style-mod';
|
|
12
|
+
/**
|
|
13
|
+
* Get the CodeMirror style for a given tag.
|
|
14
|
+
*/
|
|
15
|
+
function getStyle(tag) {
|
|
16
|
+
var _a;
|
|
17
|
+
return (_a = jupyterHighlightStyle.style([tag])) !== null && _a !== void 0 ? _a : '';
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* A component that renders JSON data as a collapsible tree.
|
|
11
21
|
*/
|
|
@@ -23,21 +33,24 @@ export class Component extends React.Component {
|
|
|
23
33
|
}, 300);
|
|
24
34
|
};
|
|
25
35
|
}
|
|
36
|
+
componentDidMount() {
|
|
37
|
+
StyleModule.mount(document, jupyterHighlightStyle.module);
|
|
38
|
+
}
|
|
26
39
|
render() {
|
|
27
40
|
const translator = this.props.translator || nullTranslator;
|
|
28
41
|
const trans = translator.load('jupyterlab');
|
|
29
|
-
const { data, metadata } = this.props;
|
|
42
|
+
const { data, metadata, forwardedRef } = this.props;
|
|
30
43
|
const root = metadata && metadata.root ? metadata.root : 'root';
|
|
31
44
|
const keyPaths = this.state.filter
|
|
32
45
|
? filterPaths(data, this.state.filter, [root])
|
|
33
46
|
: [root];
|
|
34
|
-
return (React.createElement("div", { className: "container" },
|
|
47
|
+
return (React.createElement("div", { className: "container", ref: forwardedRef },
|
|
35
48
|
React.createElement(InputGroup, { className: "filter", type: "text", placeholder: trans.__('Filter…'), onChange: this.handleChange, value: this.state.value, rightIcon: "ui-components:search" }),
|
|
36
49
|
React.createElement(JSONTree, { data: data, collectionLimit: 100, theme: {
|
|
37
50
|
extend: theme,
|
|
38
|
-
valueLabel:
|
|
39
|
-
valueText:
|
|
40
|
-
nestedNodeItemString:
|
|
51
|
+
valueLabel: getStyle(tags.variableName),
|
|
52
|
+
valueText: getStyle(tags.string),
|
|
53
|
+
nestedNodeItemString: getStyle(tags.comment)
|
|
41
54
|
}, invertTheme: false, keyPath: [root], getItemString: (type, data, itemType, itemString) => Array.isArray(data) ? (
|
|
42
55
|
// Always display array type and the number of items i.e. "[] 2 items".
|
|
43
56
|
React.createElement("span", null,
|
|
@@ -47,29 +60,19 @@ export class Component extends React.Component {
|
|
|
47
60
|
// Only display object type when it's empty i.e. "{}".
|
|
48
61
|
React.createElement("span", null, itemType)) : (null // Upstream typings don't accept null, but it should be ok
|
|
49
62
|
), labelRenderer: ([label, type]) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// className = 'cm-variable-2';
|
|
53
|
-
// }
|
|
54
|
-
// if (type === 'array') {
|
|
55
|
-
// className = 'cm-variable-2';
|
|
56
|
-
// }
|
|
57
|
-
// if (type === 'Object') {
|
|
58
|
-
// className = 'cm-variable-3';
|
|
59
|
-
// }
|
|
60
|
-
return (React.createElement("span", { className: "cm-keyword" },
|
|
61
|
-
React.createElement(Highlight, { search: this.state.filter, matchStyle: { backgroundColor: 'yellow' } }, `${label}: `)));
|
|
63
|
+
return (React.createElement("span", { className: getStyle(tags.keyword) },
|
|
64
|
+
React.createElement(Highlighter, { searchWords: [this.state.filter], textToHighlight: `${label}`, highlightClassName: "jp-mod-selected" })));
|
|
62
65
|
}, valueRenderer: raw => {
|
|
63
|
-
let className =
|
|
66
|
+
let className = getStyle(tags.string);
|
|
64
67
|
if (typeof raw === 'number') {
|
|
65
|
-
className =
|
|
68
|
+
className = getStyle(tags.number);
|
|
66
69
|
}
|
|
67
70
|
if (raw === 'true' || raw === 'false') {
|
|
68
|
-
className =
|
|
71
|
+
className = getStyle(tags.keyword);
|
|
69
72
|
}
|
|
70
73
|
return (React.createElement("span", { className: className },
|
|
71
|
-
React.createElement(
|
|
72
|
-
},
|
|
74
|
+
React.createElement(Highlighter, { searchWords: [this.state.filter], textToHighlight: `${raw}`, highlightClassName: "jp-mod-selected" })));
|
|
75
|
+
}, shouldExpandNodeInitially: (keyPath, data, level) => metadata && metadata.expanded
|
|
73
76
|
? true
|
|
74
77
|
: keyPaths.join(',').includes(keyPath.join(',')) })));
|
|
75
78
|
}
|
package/lib/component.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.tsx"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,2DAA2D;AAE3D,OAAO,EAAe,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAa,OAAO,EAAyB,MAAM,mBAAmB,CAAC;AAC9E,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.tsx"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -8,8 +8,7 @@ import { Printing } from '@jupyterlab/apputils';
|
|
|
8
8
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
9
9
|
import { Widget } from '@lumino/widgets';
|
|
10
10
|
import * as React from 'react';
|
|
11
|
-
import
|
|
12
|
-
import { Component } from './component';
|
|
11
|
+
import { createRoot } from 'react-dom/client';
|
|
13
12
|
/**
|
|
14
13
|
* The CSS class to add to the JSON Widget.
|
|
15
14
|
*/
|
|
@@ -27,9 +26,9 @@ export class RenderedJSON extends Widget {
|
|
|
27
26
|
*/
|
|
28
27
|
constructor(options) {
|
|
29
28
|
super();
|
|
29
|
+
this._rootDOM = null;
|
|
30
30
|
this.addClass(CSS_CLASS);
|
|
31
31
|
this.addClass('CodeMirror');
|
|
32
|
-
this.addClass('cm-s-jupyter');
|
|
33
32
|
this._mimeType = options.mimeType;
|
|
34
33
|
this.translator = options.translator || nullTranslator;
|
|
35
34
|
}
|
|
@@ -39,13 +38,15 @@ export class RenderedJSON extends Widget {
|
|
|
39
38
|
/**
|
|
40
39
|
* Render JSON into this widget's node.
|
|
41
40
|
*/
|
|
42
|
-
renderModel(model) {
|
|
41
|
+
async renderModel(model) {
|
|
42
|
+
const { Component } = await import('./component');
|
|
43
43
|
const data = (model.data[this._mimeType] || {});
|
|
44
44
|
const metadata = (model.metadata[this._mimeType] || {});
|
|
45
|
+
if (this._rootDOM === null) {
|
|
46
|
+
this._rootDOM = createRoot(this.node);
|
|
47
|
+
}
|
|
45
48
|
return new Promise((resolve, reject) => {
|
|
46
|
-
|
|
47
|
-
resolve();
|
|
48
|
-
});
|
|
49
|
+
this._rootDOM.render(React.createElement(Component, { data: data, metadata: metadata, translator: this.translator, forwardedRef: () => resolve() }));
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
@@ -53,7 +54,10 @@ export class RenderedJSON extends Widget {
|
|
|
53
54
|
*/
|
|
54
55
|
onBeforeDetach(msg) {
|
|
55
56
|
// Unmount the component so it can tear down.
|
|
56
|
-
|
|
57
|
+
if (this._rootDOM) {
|
|
58
|
+
this._rootDOM.unmount();
|
|
59
|
+
this._rootDOM = null;
|
|
60
|
+
}
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
/**
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,2DAA2D;AAC3D;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAe,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGtE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlab/json-extension",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-beta.0",
|
|
4
4
|
"description": "JupyterLab - JSON Renderer",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyterlab",
|
|
6
6
|
"bugs": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"lib/*.{d.ts,js,js.map}",
|
|
26
|
-
"style/*"
|
|
26
|
+
"style/*",
|
|
27
|
+
"src/**/*.{ts,tsx}"
|
|
27
28
|
],
|
|
28
29
|
"scripts": {
|
|
29
30
|
"build": "tsc -b",
|
|
@@ -32,25 +33,28 @@
|
|
|
32
33
|
"watch": "tsc -b --watch"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@jupyterlab/apputils": "^4.0.0-
|
|
36
|
-
"@jupyterlab/
|
|
37
|
-
"@jupyterlab/
|
|
38
|
-
"@jupyterlab/
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@lumino/
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"react
|
|
45
|
-
"react-
|
|
36
|
+
"@jupyterlab/apputils": "^4.0.0-beta.0",
|
|
37
|
+
"@jupyterlab/codemirror": "^4.0.0-beta.0",
|
|
38
|
+
"@jupyterlab/rendermime-interfaces": "^3.8.0-beta.0",
|
|
39
|
+
"@jupyterlab/translation": "^4.0.0-beta.0",
|
|
40
|
+
"@jupyterlab/ui-components": "^4.0.0-beta.0",
|
|
41
|
+
"@lezer/highlight": "^1.0.0",
|
|
42
|
+
"@lumino/coreutils": "^2.0.0",
|
|
43
|
+
"@lumino/messaging": "^2.0.0",
|
|
44
|
+
"@lumino/widgets": "^2.0.0",
|
|
45
|
+
"react": "^18.2.0",
|
|
46
|
+
"react-dom": "^18.2.0",
|
|
47
|
+
"react-highlight-words": "^0.20.0",
|
|
48
|
+
"react-json-tree": "^0.18.0",
|
|
49
|
+
"style-mod": "^4.0.0"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
|
-
"@types/react": "^
|
|
49
|
-
"@types/react-dom": "^
|
|
50
|
-
"@types/react-
|
|
52
|
+
"@types/react": "^18.0.26",
|
|
53
|
+
"@types/react-dom": "^18.0.9",
|
|
54
|
+
"@types/react-highlight-words": "^0.16.4",
|
|
51
55
|
"rimraf": "~3.0.0",
|
|
52
|
-
"typedoc": "~0.
|
|
53
|
-
"typescript": "~
|
|
56
|
+
"typedoc": "~0.23.25",
|
|
57
|
+
"typescript": "~5.0.2"
|
|
54
58
|
},
|
|
55
59
|
"publishConfig": {
|
|
56
60
|
"access": "public"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { jupyterHighlightStyle } from '@jupyterlab/codemirror';
|
|
5
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
6
|
+
import { InputGroup } from '@jupyterlab/ui-components';
|
|
7
|
+
import { Tag, tags } from '@lezer/highlight';
|
|
8
|
+
import { JSONArray, JSONExt, JSONObject, JSONValue } from '@lumino/coreutils';
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import Highlighter from 'react-highlight-words';
|
|
11
|
+
import { JSONTree } from 'react-json-tree';
|
|
12
|
+
import { StyleModule } from 'style-mod';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The properties for the JSON tree component.
|
|
16
|
+
*/
|
|
17
|
+
export interface IProps {
|
|
18
|
+
data: NonNullable<JSONValue>;
|
|
19
|
+
metadata?: JSONObject;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The application language translator.
|
|
23
|
+
*/
|
|
24
|
+
translator?: ITranslator;
|
|
25
|
+
forwardedRef?: React.Ref<HTMLDivElement>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The state of the JSON tree component.
|
|
30
|
+
*/
|
|
31
|
+
export interface IState {
|
|
32
|
+
filter?: string;
|
|
33
|
+
value: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the CodeMirror style for a given tag.
|
|
38
|
+
*/
|
|
39
|
+
function getStyle(tag: Tag): string {
|
|
40
|
+
return jupyterHighlightStyle.style([tag]) ?? '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A component that renders JSON data as a collapsible tree.
|
|
45
|
+
*/
|
|
46
|
+
export class Component extends React.Component<IProps, IState> {
|
|
47
|
+
state = { filter: '', value: '' };
|
|
48
|
+
|
|
49
|
+
timer: number = 0;
|
|
50
|
+
|
|
51
|
+
componentDidMount(): void {
|
|
52
|
+
StyleModule.mount(document, jupyterHighlightStyle.module as StyleModule);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
|
56
|
+
const { value } = event.target;
|
|
57
|
+
this.setState({ value });
|
|
58
|
+
window.clearTimeout(this.timer);
|
|
59
|
+
this.timer = window.setTimeout(() => {
|
|
60
|
+
this.setState({ filter: value });
|
|
61
|
+
}, 300);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
render(): JSX.Element {
|
|
65
|
+
const translator = this.props.translator || nullTranslator;
|
|
66
|
+
const trans = translator.load('jupyterlab');
|
|
67
|
+
|
|
68
|
+
const { data, metadata, forwardedRef } = this.props;
|
|
69
|
+
const root = metadata && metadata.root ? (metadata.root as string) : 'root';
|
|
70
|
+
const keyPaths = this.state.filter
|
|
71
|
+
? filterPaths(data, this.state.filter, [root])
|
|
72
|
+
: [root];
|
|
73
|
+
return (
|
|
74
|
+
<div className="container" ref={forwardedRef}>
|
|
75
|
+
<InputGroup
|
|
76
|
+
className="filter"
|
|
77
|
+
type="text"
|
|
78
|
+
placeholder={trans.__('Filter…')}
|
|
79
|
+
onChange={this.handleChange}
|
|
80
|
+
value={this.state.value}
|
|
81
|
+
rightIcon="ui-components:search"
|
|
82
|
+
/>
|
|
83
|
+
<JSONTree
|
|
84
|
+
data={data}
|
|
85
|
+
collectionLimit={100}
|
|
86
|
+
theme={{
|
|
87
|
+
extend: theme,
|
|
88
|
+
valueLabel: getStyle(tags.variableName),
|
|
89
|
+
valueText: getStyle(tags.string),
|
|
90
|
+
nestedNodeItemString: getStyle(tags.comment)
|
|
91
|
+
}}
|
|
92
|
+
invertTheme={false}
|
|
93
|
+
keyPath={[root]}
|
|
94
|
+
getItemString={(type, data, itemType, itemString) =>
|
|
95
|
+
Array.isArray(data) ? (
|
|
96
|
+
// Always display array type and the number of items i.e. "[] 2 items".
|
|
97
|
+
<span>
|
|
98
|
+
{itemType} {itemString}
|
|
99
|
+
</span>
|
|
100
|
+
) : Object.keys(data as object).length === 0 ? (
|
|
101
|
+
// Only display object type when it's empty i.e. "{}".
|
|
102
|
+
<span>{itemType}</span>
|
|
103
|
+
) : (
|
|
104
|
+
null! // Upstream typings don't accept null, but it should be ok
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
labelRenderer={([label, type]) => {
|
|
108
|
+
return (
|
|
109
|
+
<span className={getStyle(tags.keyword)}>
|
|
110
|
+
<Highlighter
|
|
111
|
+
searchWords={[this.state.filter]}
|
|
112
|
+
textToHighlight={`${label}`}
|
|
113
|
+
highlightClassName="jp-mod-selected"
|
|
114
|
+
></Highlighter>
|
|
115
|
+
</span>
|
|
116
|
+
);
|
|
117
|
+
}}
|
|
118
|
+
valueRenderer={raw => {
|
|
119
|
+
let className = getStyle(tags.string);
|
|
120
|
+
if (typeof raw === 'number') {
|
|
121
|
+
className = getStyle(tags.number);
|
|
122
|
+
}
|
|
123
|
+
if (raw === 'true' || raw === 'false') {
|
|
124
|
+
className = getStyle(tags.keyword);
|
|
125
|
+
}
|
|
126
|
+
return (
|
|
127
|
+
<span className={className}>
|
|
128
|
+
<Highlighter
|
|
129
|
+
searchWords={[this.state.filter]}
|
|
130
|
+
textToHighlight={`${raw}`}
|
|
131
|
+
highlightClassName="jp-mod-selected"
|
|
132
|
+
></Highlighter>
|
|
133
|
+
</span>
|
|
134
|
+
);
|
|
135
|
+
}}
|
|
136
|
+
shouldExpandNodeInitially={(keyPath, data, level) =>
|
|
137
|
+
metadata && metadata.expanded
|
|
138
|
+
? true
|
|
139
|
+
: keyPaths.join(',').includes(keyPath.join(','))
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Provide an invalid theme object (this is on purpose!) to invalidate the
|
|
148
|
+
// react-json-tree's inline styles that override CodeMirror CSS classes
|
|
149
|
+
const theme = {
|
|
150
|
+
scheme: 'jupyter',
|
|
151
|
+
base00: 'invalid',
|
|
152
|
+
base01: 'invalid',
|
|
153
|
+
base02: 'invalid',
|
|
154
|
+
base03: 'invalid',
|
|
155
|
+
base04: 'invalid',
|
|
156
|
+
base05: 'invalid',
|
|
157
|
+
base06: 'invalid',
|
|
158
|
+
base07: 'invalid',
|
|
159
|
+
base08: 'invalid',
|
|
160
|
+
base09: 'invalid',
|
|
161
|
+
base0A: 'invalid',
|
|
162
|
+
base0B: 'invalid',
|
|
163
|
+
base0C: 'invalid',
|
|
164
|
+
base0D: 'invalid',
|
|
165
|
+
base0E: 'invalid',
|
|
166
|
+
base0F: 'invalid',
|
|
167
|
+
author: 'invalid'
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function objectIncludes(data: JSONValue, query: string): boolean {
|
|
171
|
+
return JSON.stringify(data).includes(query);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function filterPaths(
|
|
175
|
+
data: NonNullable<JSONValue>,
|
|
176
|
+
query: string,
|
|
177
|
+
parent: JSONArray = ['root']
|
|
178
|
+
): JSONArray {
|
|
179
|
+
if (JSONExt.isArray(data)) {
|
|
180
|
+
return data.reduce((result: JSONArray, item: JSONValue, index: number) => {
|
|
181
|
+
if (item && typeof item === 'object' && objectIncludes(item, query)) {
|
|
182
|
+
return [
|
|
183
|
+
...result,
|
|
184
|
+
[index, ...parent].join(','),
|
|
185
|
+
...filterPaths(item, query, [index, ...parent])
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}, []) as JSONArray;
|
|
190
|
+
}
|
|
191
|
+
if (JSONExt.isObject(data)) {
|
|
192
|
+
return Object.keys(data).reduce((result: JSONArray, key: string) => {
|
|
193
|
+
const item = data[key];
|
|
194
|
+
if (
|
|
195
|
+
item &&
|
|
196
|
+
typeof item === 'object' &&
|
|
197
|
+
(key.includes(query) || objectIncludes(item, query))
|
|
198
|
+
) {
|
|
199
|
+
return [
|
|
200
|
+
...result,
|
|
201
|
+
[key, ...parent].join(','),
|
|
202
|
+
...filterPaths(item, query, [key, ...parent])
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}, []);
|
|
207
|
+
}
|
|
208
|
+
return [];
|
|
209
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/**
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
* @module json-extension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Printing } from '@jupyterlab/apputils';
|
|
9
|
+
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
10
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
11
|
+
import { JSONObject, JSONValue } from '@lumino/coreutils';
|
|
12
|
+
import { Message } from '@lumino/messaging';
|
|
13
|
+
import { Widget } from '@lumino/widgets';
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The CSS class to add to the JSON Widget.
|
|
19
|
+
*/
|
|
20
|
+
const CSS_CLASS = 'jp-RenderedJSON';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The MIME type for JSON.
|
|
24
|
+
*/
|
|
25
|
+
export const MIME_TYPE = 'application/json';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A renderer for JSON data.
|
|
29
|
+
*/
|
|
30
|
+
export class RenderedJSON
|
|
31
|
+
extends Widget
|
|
32
|
+
implements IRenderMime.IRenderer, Printing.IPrintable
|
|
33
|
+
{
|
|
34
|
+
/**
|
|
35
|
+
* Create a new widget for rendering JSON.
|
|
36
|
+
*/
|
|
37
|
+
constructor(options: IRenderMime.IRendererOptions) {
|
|
38
|
+
super();
|
|
39
|
+
this.addClass(CSS_CLASS);
|
|
40
|
+
this.addClass('CodeMirror');
|
|
41
|
+
this._mimeType = options.mimeType;
|
|
42
|
+
this.translator = options.translator || nullTranslator;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[Printing.symbol]() {
|
|
46
|
+
return (): Promise<void> => Printing.printWidget(this);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render JSON into this widget's node.
|
|
51
|
+
*/
|
|
52
|
+
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
|
|
53
|
+
const { Component } = await import('./component');
|
|
54
|
+
const data = (model.data[this._mimeType] || {}) as NonNullable<JSONValue>;
|
|
55
|
+
const metadata = (model.metadata[this._mimeType] || {}) as JSONObject;
|
|
56
|
+
if (this._rootDOM === null) {
|
|
57
|
+
this._rootDOM = createRoot(this.node);
|
|
58
|
+
}
|
|
59
|
+
return new Promise<void>((resolve, reject) => {
|
|
60
|
+
this._rootDOM!.render(
|
|
61
|
+
<Component
|
|
62
|
+
data={data}
|
|
63
|
+
metadata={metadata}
|
|
64
|
+
translator={this.translator}
|
|
65
|
+
forwardedRef={() => resolve()}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Called before the widget is detached from the DOM.
|
|
73
|
+
*/
|
|
74
|
+
protected onBeforeDetach(msg: Message): void {
|
|
75
|
+
// Unmount the component so it can tear down.
|
|
76
|
+
if (this._rootDOM) {
|
|
77
|
+
this._rootDOM.unmount();
|
|
78
|
+
this._rootDOM = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
translator: ITranslator;
|
|
83
|
+
private _mimeType: string;
|
|
84
|
+
private _rootDOM: Root | null = null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A mime renderer factory for JSON data.
|
|
89
|
+
*/
|
|
90
|
+
export const rendererFactory: IRenderMime.IRendererFactory = {
|
|
91
|
+
safe: true,
|
|
92
|
+
mimeTypes: [MIME_TYPE],
|
|
93
|
+
createRenderer: options => new RenderedJSON(options)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const extensions: IRenderMime.IExtension | IRenderMime.IExtension[] = [
|
|
97
|
+
{
|
|
98
|
+
id: '@jupyterlab/json-extension:factory',
|
|
99
|
+
rendererFactory,
|
|
100
|
+
rank: 0,
|
|
101
|
+
dataType: 'json',
|
|
102
|
+
documentWidgetFactoryOptions: {
|
|
103
|
+
name: 'JSON',
|
|
104
|
+
// TODO: how to translate label of the factory?
|
|
105
|
+
primaryFileType: 'json',
|
|
106
|
+
fileTypes: ['json', 'notebook', 'geojson'],
|
|
107
|
+
defaultFor: ['json']
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
export default extensions;
|
package/style/base.css
CHANGED
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
overflow-y: auto;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
.jp-RenderedJSON mark.jp-mod-selected {
|
|
45
|
+
background-color: var(--jp-search-unselected-match-background-color);
|
|
46
|
+
color: var(--jp-search-unselected-match-color);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
/* Output styles */
|
|
45
50
|
|
|
46
51
|
/* .jp-OutputArea .jp-RenderedJSON {
|
package/style/index.css
CHANGED