mirror of https://github.com/grafana/grafana
Panel Inspect: use monaco for json display (#25251)
parent
dcd5752086
commit
1a711e7df0
@ -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} />; |
||||
}; |
@ -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, |
||||
}; |
||||
}; |
@ -0,0 +1 @@ |
||||
export const style = 'style'; |
Loading…
Reference in new issue