Panel Inspect: use monaco for json display (#25251)
This commit is contained in:
@@ -180,6 +180,8 @@ const getBaseWebpackConfig: WebpackConfigurationGetter = async options => {
|
||||
'@grafana/ui',
|
||||
'@grafana/runtime',
|
||||
'@grafana/data',
|
||||
'monaco-editor',
|
||||
'react-monaco-editor',
|
||||
// @ts-ignore
|
||||
(context, request, callback) => {
|
||||
const prefix = 'grafana/';
|
||||
|
||||
@@ -78,12 +78,36 @@ module.exports = ({ config, mode }) => {
|
||||
|
||||
config.optimization = {
|
||||
nodeEnv: 'production',
|
||||
moduleIds: 'hashed',
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minChunks: 1,
|
||||
cacheGroups: {
|
||||
monaco: {
|
||||
test: /[\\/]node_modules[\\/](monaco-editor)[\\/].*[jt]sx?$/,
|
||||
chunks: 'initial',
|
||||
priority: 20,
|
||||
enforce: true,
|
||||
},
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/].*[jt]sx?$/,
|
||||
chunks: 'initial',
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
enforce: true,
|
||||
},
|
||||
default: {
|
||||
priority: -20,
|
||||
chunks: 'all',
|
||||
test: /.*[jt]sx?$/,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: false,
|
||||
parallel: false,
|
||||
sourceMap: false,
|
||||
}),
|
||||
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco/ }),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
"immutable": "3.8.2",
|
||||
"jquery": "3.5.1",
|
||||
"lodash": "4.17.15",
|
||||
"monaco-editor": "0.20.0",
|
||||
"react-monaco-editor": "0.36.0",
|
||||
"moment": "2.24.0",
|
||||
"papaparse": "4.6.3",
|
||||
"rc-cascader": "1.0.1",
|
||||
|
||||
@@ -25,7 +25,16 @@ const buildCjsPackage = ({ env }) => {
|
||||
},
|
||||
},
|
||||
],
|
||||
external: ['react', 'react-dom', '@grafana/data', 'moment', '@grafana/e2e-selectors'],
|
||||
external: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'@grafana/data',
|
||||
'@grafana/e2e-selectors',
|
||||
'moment',
|
||||
'monaco-editor', // Monaco should not be used directly
|
||||
'monaco-editor/esm/vs/editor/editor.api', // Monaco should not be used directly
|
||||
'react-monaco-editor',
|
||||
],
|
||||
plugins: [
|
||||
commonjs({
|
||||
include: /node_modules/,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||
import { CodeEditor } from './CodeEditor';
|
||||
|
||||
<Meta title="MDX|CodeEditor" component={CodeEditor} />
|
||||
|
||||
# CodeEditor
|
||||
|
||||
Monaco Code editor
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import mdx from './CodeEditor.mdx';
|
||||
import CodeEditor from './CodeEditor';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
text: text('Body', 'SELECT * FROM table LIMIT 10'),
|
||||
language: text('Language', 'sql'),
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'CodeEditor',
|
||||
component: CodeEditor,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const basic = () => {
|
||||
const { text, language } = getKnobs();
|
||||
return (
|
||||
<CodeEditor
|
||||
value={text}
|
||||
language={language}
|
||||
onBlur={(text: string) => {
|
||||
console.log('Blur: ', text);
|
||||
action('code blur')(text);
|
||||
}}
|
||||
onSave={(text: string) => {
|
||||
console.log('Save: ', text);
|
||||
action('code saved')(text);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { withTheme } from '../../themes';
|
||||
import { Themeable } from '../../types';
|
||||
import { KeyCode, editor, KeyMod } from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import ReactMonaco from 'react-monaco-editor';
|
||||
|
||||
export interface CodeEditorProps {
|
||||
value: string;
|
||||
language: string;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
|
||||
readOnly?: boolean;
|
||||
showMiniMap?: boolean;
|
||||
|
||||
/**
|
||||
* Callback after the editor has mounted that gives you raw access to monaco
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
onEditorDidMount?: (editor: editor.IStandaloneCodeEditor) => void;
|
||||
|
||||
/** Handler to be performed when editor is blurred */
|
||||
onBlur?: CodeEditorChangeHandler;
|
||||
|
||||
/** Handler to be performed when Cmd/Ctrl+S is pressed */
|
||||
onSave?: CodeEditorChangeHandler;
|
||||
}
|
||||
|
||||
type Props = CodeEditorProps & Themeable;
|
||||
|
||||
class UnthemedCodeEditor extends React.PureComponent<Props> {
|
||||
getEditorValue = () => '';
|
||||
|
||||
onBlur = () => {
|
||||
const { onBlur } = this.props;
|
||||
if (onBlur) {
|
||||
onBlur(this.getEditorValue());
|
||||
}
|
||||
};
|
||||
|
||||
editorDidMount = (editor: editor.IStandaloneCodeEditor) => {
|
||||
const { onSave, onEditorDidMount } = this.props;
|
||||
|
||||
this.getEditorValue = () => editor.getValue();
|
||||
|
||||
if (onSave) {
|
||||
editor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => {
|
||||
onSave(this.getEditorValue());
|
||||
});
|
||||
}
|
||||
|
||||
if (onEditorDidMount) {
|
||||
onEditorDidMount(editor);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme, language, width, height, showMiniMap, readOnly } = this.props;
|
||||
const value = this.props.value ?? '';
|
||||
const longText = value.length > 100;
|
||||
|
||||
return (
|
||||
<div onBlur={this.onBlur}>
|
||||
<ReactMonaco
|
||||
width={width}
|
||||
height={height}
|
||||
language={language}
|
||||
theme={theme.isDark ? 'vs-dark' : 'vs-light'}
|
||||
value={value}
|
||||
options={{
|
||||
wordWrap: 'off',
|
||||
codeLens: false, // not included in the bundle
|
||||
minimap: {
|
||||
enabled: longText && showMiniMap,
|
||||
renderCharacters: false,
|
||||
},
|
||||
readOnly,
|
||||
lineNumbersMinChars: 4,
|
||||
lineDecorationsWidth: 0,
|
||||
overviewRulerBorder: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
editorDidMount={this.editorDidMount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type CodeEditorChangeHandler = (value: string) => void;
|
||||
export default withTheme(UnthemedCodeEditor);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { useAsyncDependency } from '../../utils/useAsyncDependency';
|
||||
import { ErrorWithStack, LoadingPlaceholder } from '..';
|
||||
import { CodeEditorProps } from './CodeEditor';
|
||||
|
||||
export type CodeEditorChangeHandler = (value: string) => void;
|
||||
|
||||
export const CodeEditor: React.FC<CodeEditorProps> = props => {
|
||||
const { loading, error, dependency } = useAsyncDependency(
|
||||
import(/* webpackChunkName: "code-editor" */ './CodeEditor')
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder text={'Loading...'} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorWithStack
|
||||
title="Code editor failed to load"
|
||||
error={error}
|
||||
errorInfo={{ componentStack: error?.stack || '' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const CodeEditor = dependency.default;
|
||||
return <CodeEditor {...props} />;
|
||||
};
|
||||
@@ -34,6 +34,7 @@ export { FilterPill } from './FilterPill/FilterPill';
|
||||
|
||||
export { ConfirmModal } from './ConfirmModal/ConfirmModal';
|
||||
export { QueryField } from './QueryField/QueryField';
|
||||
export { CodeEditor } from './Monaco/CodeEditorLazy';
|
||||
|
||||
// TODO: namespace
|
||||
export { Modal } from './Modal/Modal';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
// Allows simple dynamic imports in the components
|
||||
export const useAsyncDependency = (importStatement: Promise<any>) => {
|
||||
const state = useAsync(async () => {
|
||||
return await importStatement;
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
dependency: state.value,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user