Skip to content

Commit 625f939

Browse files
authored
feat: jumpToPath plugin with Monaco (#3244)
* requires [email protected] or greater * maps position from apidom-ls to monaco interface * maintains legacy compatibility with `path` and `specPath` props
1 parent 1a0c9d4 commit 625f939

File tree

14 files changed

+326
-21
lines changed

14 files changed

+326
-21
lines changed

package-lock.json

Lines changed: 25 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
}
3333
},
3434
"scripts": {
35-
"start": "cross-env DISABLE_ESLINT_PLUGIN=false ENABLE_PROGRESS_PLUGIN=true react-scripts start",
35+
"start": "cross-env DISABLE_ESLINT_PLUGIN=true ENABLE_PROGRESS_PLUGIN=true react-scripts start",
3636
"build": "npm run build:app && npm run build:bundle:esm && npm run build:bundle:umd",
3737
"build:app": "cross-env ENABLE_PROGRESS_PLUGIN=false DISABLE_ESLINT_PLUGIN=false react-scripts build",
3838
"build:app:serve": "serve -s build -l 3050",
@@ -66,6 +66,7 @@
6666
"axios": "^0.27.2",
6767
"deepmerge": "^4.2.2",
6868
"file-dialog": "^0.0.8",
69+
"immutable": "^3.8.2",
6970
"is-json": "^2.0.1",
7071
"js-file-download": "^0.4.12",
7172
"js-yaml": "^4.1.0",
@@ -77,6 +78,7 @@
7778
"react-collapse-pane": "^3.0.1",
7879
"react-dom": "^17.0.2",
7980
"react-dropzone": "^14.2.2",
81+
"react-immutable-proptypes": "^2.2.0",
8082
"react-modal": "^3.15.1",
8183
"react-resize-detector": "^7.1.2",
8284
"react-table": "^7.8.0",

src/App.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import EditorPreviewAsyncAPIPlugin from './plugins/editor-preview-asyncapi/index
1717
import EditorReadOnlyPlugin from './plugins/editor-read-only/index.js';
1818
import EditorSpecOriginPlugin from './plugins/editor-spec-origin/index.js';
1919
import EditorPersistencePlugin from './plugins/editor-persistence/index.js';
20+
import EditorJumpFromPathToLinePlugin from './plugins/editor-jump-from-path-to-line/index.js';
2021

2122
const SafeRenderPlugin = (system) =>
2223
SwaggerUI.plugins.SafeRender({
@@ -29,6 +30,7 @@ const SafeRenderPlugin = (system) =>
2930
'EditorPane',
3031
'EditorPaneBarTop',
3132
'EditorPreviewPane',
33+
'EditorJumpFromPathToLine',
3234
'ValidationPane',
3335
'AlertDialog',
3436
'ConfirmDialog',
@@ -59,6 +61,7 @@ SwaggerEditor.plugins = {
5961
EditorPersistence: EditorPersistencePlugin,
6062
EditorPreviewSwaggerUI: EditorPreviewSwaggerUIPlugin,
6163
EditorPreviewAsyncAPI: EditorPreviewAsyncAPIPlugin,
64+
EditorJumpFromPathToLine: EditorJumpFromPathToLinePlugin,
6265
Topbar: TopbarPlugin,
6366
Layout: LayoutPlugin,
6467
};
@@ -74,6 +77,7 @@ SwaggerEditor.presets = {
7477
EditorPersistencePlugin,
7578
EditorPreviewSwaggerUIPlugin,
7679
EditorPreviewAsyncAPIPlugin,
80+
EditorJumpFromPathToLinePlugin,
7781
TopbarPlugin,
7882
LayoutPlugin,
7983
SafeRenderPlugin,
@@ -90,6 +94,7 @@ SwaggerEditor.presets = {
9094
EditorPersistencePlugin,
9195
EditorPreviewSwaggerUIPlugin,
9296
EditorPreviewAsyncAPIPlugin,
97+
EditorJumpFromPathToLinePlugin,
9398
TopbarPlugin,
9499
LayoutPlugin,
95100
SafeRenderPlugin,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import ImmutablePropTypes from 'react-immutable-proptypes';
4+
5+
import JumpIcon from './jump-icon.svg';
6+
7+
/**
8+
* JumpToPath is a legacy name that is already built into SwaggerUI
9+
* 1. onHover of SwaggerUI operation or model, render svg icon
10+
* 2. onClick of svg icon => jump to line in editor
11+
*/
12+
13+
const JumpToPath = ({ specSelectors, editorActions, path, specPath, content, showButton }) => {
14+
const handleJumpToEditorLine = (e) => {
15+
e.stopPropagation();
16+
const jumpPath = specSelectors.bestJumpPath({ path, specPath });
17+
// `apidom-ls` will expect `jumpPath` to be a String instead of legacy Array, e.g. '/components/schemas/Category/properties/id'
18+
// `Editor` will handle the rest of workflow
19+
editorActions.setRequestJumpToEditorMarker({ jsonPointer: jumpPath });
20+
};
21+
22+
const defaultJumpButton = (
23+
<div
24+
role="button"
25+
tabIndex={0}
26+
onClick={handleJumpToEditorLine}
27+
onKeyPress={handleJumpToEditorLine}
28+
>
29+
<img src={JumpIcon} className="view-line-link" title="Jump to definition" alt="" />
30+
</div>
31+
);
32+
33+
if (content) {
34+
// if we were given content to render, wrap it
35+
return (
36+
<span
37+
role="button"
38+
tabIndex={0}
39+
onClick={handleJumpToEditorLine}
40+
onKeyPress={handleJumpToEditorLine}
41+
>
42+
{showButton ? { defaultJumpButton } : null}
43+
{content}
44+
</span>
45+
);
46+
}
47+
return <div>{defaultJumpButton}</div>;
48+
};
49+
50+
JumpToPath.propTypes = {
51+
editorActions: PropTypes.oneOfType([PropTypes.object]).isRequired,
52+
specSelectors: PropTypes.oneOfType([PropTypes.object]).isRequired,
53+
path: PropTypes.oneOfType([PropTypes.array, PropTypes.string, ImmutablePropTypes.list]),
54+
specPath: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]), // the location within the spec. used as a fallback if `path` doesn't exist
55+
content: PropTypes.element,
56+
showButton: PropTypes.bool,
57+
};
58+
59+
JumpToPath.defaultProps = {
60+
path: [],
61+
specPath: [],
62+
content: null,
63+
showButton: false,
64+
};
65+
66+
export default JumpToPath;
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import JumpToPath from './components/JumpToPath.jsx';
2+
import { bestJumpPath } from './selectors.js';
3+
4+
const EditorJumpFromPathToLine = () => ({
5+
components: {
6+
JumpToPath,
7+
},
8+
statePlugins: {
9+
spec: {
10+
selectors: {
11+
bestJumpPath,
12+
},
13+
},
14+
},
15+
});
16+
17+
export default EditorJumpFromPathToLine;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-disable import/prefer-default-export */
2+
import { createSelector } from 'reselect';
3+
import { List } from 'immutable';
4+
5+
const transformArrayToStringPath = (arr) => {
6+
if (arr.at(0) !== '/') {
7+
return `/${arr.join('')}`;
8+
}
9+
return arr.join('/');
10+
};
11+
12+
const transformImListToStringPath = (list) => {
13+
const arr = list.toJS();
14+
if (arr.at(0) !== '/') {
15+
return `/${arr.join('')}`;
16+
}
17+
return arr.join('');
18+
};
19+
20+
// compared to `path`, each element inside `specPath` does not contain leading `/`
21+
const transformArrayToStringSpecPath = (arr) => {
22+
if (arr.at(0) !== '/') {
23+
return `/${arr.join('/')}`;
24+
}
25+
return arr.join('/');
26+
};
27+
28+
// compared to `path`, each element inside `specPath` does not contain leading `/`
29+
const transformImListToStringSpecPath = (list) => {
30+
const arr = list.toJS();
31+
if (arr.at(0) !== '/') {
32+
return `/${arr.join('/')}`;
33+
}
34+
return arr.join('/');
35+
};
36+
37+
// apidom-ls expects a String as arg
38+
export const bestJumpPath = createSelector(
39+
(state, pathObj) => pathObj, // input from arg to forward to output
40+
({ path, specPath }) => {
41+
if (path && typeof path === 'string') {
42+
return path;
43+
}
44+
if (path && Array.isArray(path) && path.length > 0) {
45+
return transformArrayToStringPath(path);
46+
}
47+
if (path && List.isList(path) && path.size > 0) {
48+
return transformImListToStringPath(path);
49+
}
50+
if (specPath && typeof specPath === 'string') {
51+
return specPath;
52+
}
53+
if (specPath && Array.isArray(specPath) && specPath.length > 0) {
54+
return transformArrayToStringSpecPath(specPath);
55+
}
56+
if (specPath && List.isList(specPath) && specPath.size > 0) {
57+
return transformImListToStringSpecPath(specPath);
58+
}
59+
return '';
60+
}
61+
);

src/plugins/editor-monaco/actions.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ export const EDITOR_UPDATE_THEME = 'editor_update_theme';
22
export const EDITOR_ERROR_MARKERS = 'editor_error_markers';
33
export const EDITOR_JUMP_TO_EDITOR_MARKER = 'editor_jump_to_editor_marker';
44
export const EDITOR_CLEAR_JUMP_TO_EDITOR_MARKER = 'editor_clear_jump_to_editor_marker';
5+
export const EDITOR_SET_REQUEST_JUMP_TO_EDITOR_MARKER = 'editor_set_request_jump_to_editor_marker';
6+
export const EDITOR_CLEAR_REQUEST_JUMP_TO_EDITOR_MARKER =
7+
'editor_clear_request_jump_to_editor_marker';
58

69
export const updateEditorTheme = (theme = 'my-vs-dark') => {
710
return {
@@ -27,3 +30,15 @@ export const clearJumpToEditorMarker = () => {
2730
type: EDITOR_CLEAR_JUMP_TO_EDITOR_MARKER,
2831
};
2932
};
33+
export const setRequestJumpToEditorMarker = (marker = {}) => {
34+
return {
35+
payload: marker,
36+
type: EDITOR_SET_REQUEST_JUMP_TO_EDITOR_MARKER,
37+
};
38+
};
39+
export const clearRequestJumpToEditorMarker = () => {
40+
return {
41+
payload: {},
42+
type: EDITOR_CLEAR_REQUEST_JUMP_TO_EDITOR_MARKER,
43+
};
44+
};

0 commit comments

Comments
 (0)