mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime monitor management] Add full screen/copy button ability in browser inline script editing (#124500)
This commit is contained in:
parent
157f8e1bbb
commit
e93a651c1d
4 changed files with 228 additions and 71 deletions
|
@ -126,6 +126,7 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
>
|
||||
<div
|
||||
className="kibanaCodeEditor"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<EuiToolTip
|
||||
content={
|
||||
|
@ -181,47 +182,50 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
/>
|
||||
</span>
|
||||
</EuiToolTip>
|
||||
<mockMonacoEditor
|
||||
editorDidMount={[Function]}
|
||||
editorWillMount={[Function]}
|
||||
height={250}
|
||||
language="loglang"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Object {
|
||||
"fontFamily": "Roboto Mono",
|
||||
"fontSize": 12,
|
||||
"lineHeight": 21,
|
||||
"matchBrackets": "never",
|
||||
"minimap": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
"renderLineHighlight": "none",
|
||||
"scrollBeyondLastLine": false,
|
||||
"scrollbar": Object {
|
||||
"alwaysConsumeMouseWheel": false,
|
||||
"useShadows": false,
|
||||
},
|
||||
"wordBasedSuggestions": false,
|
||||
"wordWrap": "on",
|
||||
"wrappingIndent": "indent",
|
||||
<Component>
|
||||
<mockMonacoEditor
|
||||
editorDidMount={[Function]}
|
||||
editorWillMount={[Function]}
|
||||
height={250}
|
||||
language="loglang"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Object {
|
||||
"fontFamily": "Roboto Mono",
|
||||
"fontSize": 12,
|
||||
"lineHeight": 21,
|
||||
"matchBrackets": "never",
|
||||
"minimap": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
"padding": Object {},
|
||||
"renderLineHighlight": "none",
|
||||
"scrollBeyondLastLine": false,
|
||||
"scrollbar": Object {
|
||||
"alwaysConsumeMouseWheel": false,
|
||||
"useShadows": false,
|
||||
},
|
||||
"wordBasedSuggestions": false,
|
||||
"wordWrap": "on",
|
||||
"wrappingIndent": "indent",
|
||||
}
|
||||
}
|
||||
}
|
||||
theme="euiColors"
|
||||
value="
|
||||
theme="euiColors"
|
||||
value="
|
||||
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
|
||||
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
|
||||
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div />
|
||||
<textarea
|
||||
data-test-subj="monacoEditorTextarea"
|
||||
onKeyDown={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</mockMonacoEditor>
|
||||
>
|
||||
<div>
|
||||
<div />
|
||||
<textarea
|
||||
data-test-subj="monacoEditorTextarea"
|
||||
onKeyDown={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</mockMonacoEditor>
|
||||
</Component>
|
||||
<ResizeDetector
|
||||
handleHeight={true}
|
||||
handleWidth={true}
|
||||
|
|
|
@ -6,10 +6,21 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import React, { useState, useRef, useCallback, useMemo, useEffect, KeyboardEvent } from 'react';
|
||||
import ReactResizeDetector from 'react-resize-detector';
|
||||
import ReactMonacoEditor from 'react-monaco-editor';
|
||||
import { htmlIdGenerator, EuiToolTip, keys } from '@elastic/eui';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiToolTip,
|
||||
keys,
|
||||
EuiButtonIcon,
|
||||
EuiOverlayMask,
|
||||
EuiI18n,
|
||||
EuiFocusTrap,
|
||||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -114,6 +125,9 @@ export interface Props {
|
|||
* Accessible name for the editor. (Defaults to "Code editor")
|
||||
*/
|
||||
'aria-label'?: string;
|
||||
|
||||
isCopyable?: boolean;
|
||||
allowFullScreen?: boolean;
|
||||
}
|
||||
|
||||
export const CodeEditor: React.FC<Props> = ({
|
||||
|
@ -136,6 +150,8 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
'aria-label': ariaLabel = i18n.translate('kibana-react.kibanaCodeEditor.ariaLabel', {
|
||||
defaultMessage: 'Code Editor',
|
||||
}),
|
||||
isCopyable = false,
|
||||
allowFullScreen = false,
|
||||
}) => {
|
||||
// We need to be able to mock the MonacoEditor in our test in order to not test implementation
|
||||
// detail and not have to call methods on the <CodeEditor /> component instance.
|
||||
|
@ -147,6 +163,11 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
: ReactMonacoEditor;
|
||||
}, []);
|
||||
|
||||
const { FullScreenDisplay, FullScreenButton, isFullScreen, setIsFullScreen, onKeyDown } =
|
||||
useFullScreen({
|
||||
allowFullScreen,
|
||||
});
|
||||
|
||||
const isReadOnly = options?.readOnly ?? false;
|
||||
|
||||
const _editor = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||||
|
@ -197,6 +218,7 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
stopEditing();
|
||||
editorHint.current?.focus();
|
||||
}
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
},
|
||||
[stopEditing]
|
||||
|
@ -396,42 +418,59 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
};
|
||||
}, [placeholder, value]);
|
||||
|
||||
const { CopyButton } = useCopy({ isCopyable, value });
|
||||
|
||||
return (
|
||||
<div className="kibanaCodeEditor">
|
||||
<div className="kibanaCodeEditor" onKeyDown={onKeyDown}>
|
||||
{renderPrompt()}
|
||||
|
||||
<MonacoEditor
|
||||
theme={transparentBackground ? 'euiColorsTransparent' : 'euiColors'}
|
||||
language={languageId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
width={width}
|
||||
height={height}
|
||||
editorWillMount={_editorWillMount}
|
||||
editorDidMount={_editorDidMount}
|
||||
options={{
|
||||
renderLineHighlight: 'none',
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
// Scroll events are handled only when there is scrollable content. When there is scrollable content, the
|
||||
// editor should scroll to the bottom then break out of that scroll context and continue scrolling on any
|
||||
// outer scrollbars.
|
||||
alwaysConsumeMouseWheel: false,
|
||||
},
|
||||
wordBasedSuggestions: false,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'indent',
|
||||
matchBrackets: 'never',
|
||||
fontFamily: 'Roboto Mono',
|
||||
fontSize: 12,
|
||||
lineHeight: 21,
|
||||
...options,
|
||||
}}
|
||||
/>
|
||||
<FullScreenDisplay>
|
||||
{allowFullScreen || isCopyable ? (
|
||||
<div className="kibanaCodeEditor__controls">
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<CopyButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FullScreenButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
) : null}
|
||||
<MonacoEditor
|
||||
theme={transparentBackground ? 'euiColorsTransparent' : 'euiColors'}
|
||||
language={languageId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
width={isFullScreen ? '100vw' : width}
|
||||
height={isFullScreen ? '100vh' : height}
|
||||
editorWillMount={_editorWillMount}
|
||||
editorDidMount={_editorDidMount}
|
||||
options={{
|
||||
padding: allowFullScreen || isCopyable ? { top: 24 } : {},
|
||||
renderLineHighlight: 'none',
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
// Scroll events are handled only when there is scrollable content. When there is scrollable content, the
|
||||
// editor should scroll to the bottom then break out of that scroll context and continue scrolling on any
|
||||
// outer scrollbars.
|
||||
alwaysConsumeMouseWheel: false,
|
||||
},
|
||||
wordBasedSuggestions: false,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'indent',
|
||||
matchBrackets: 'never',
|
||||
fontFamily: 'Roboto Mono',
|
||||
fontSize: isFullScreen ? 16 : 12,
|
||||
lineHeight: isFullScreen ? 24 : 21,
|
||||
...options,
|
||||
}}
|
||||
/>
|
||||
</FullScreenDisplay>
|
||||
<ReactResizeDetector
|
||||
handleWidth
|
||||
handleHeight
|
||||
|
@ -442,6 +481,101 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fullscreen logic
|
||||
*/
|
||||
|
||||
const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => {
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
setIsFullScreen(!isFullScreen);
|
||||
};
|
||||
|
||||
const onKeyDown = useCallback((event: KeyboardEvent<HTMLElement>) => {
|
||||
if (event.key === keys.ESCAPE) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const FullScreenButton: React.FC = () => {
|
||||
if (!allowFullScreen) return null;
|
||||
return (
|
||||
<EuiI18n
|
||||
tokens={['euiCodeBlock.fullscreenCollapse', 'euiCodeBlock.fullscreenExpand']}
|
||||
defaults={['Collapse', 'Expand']}
|
||||
>
|
||||
{([fullscreenCollapse, fullscreenExpand]: string[]) => (
|
||||
<EuiButtonIcon
|
||||
className="euiCodeBlock__fullScreenButton"
|
||||
onClick={toggleFullScreen}
|
||||
iconType={isFullScreen ? 'fullScreenExit' : 'fullScreen'}
|
||||
color="text"
|
||||
aria-label={isFullScreen ? fullscreenCollapse : fullscreenExpand}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</EuiI18n>
|
||||
);
|
||||
};
|
||||
|
||||
const FullScreenDisplay = useMemo(
|
||||
() =>
|
||||
({ children }: { children: Array<JSX.Element | null> | JSX.Element }) => {
|
||||
if (!isFullScreen) return <>{children}</>;
|
||||
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiFocusTrap clickOutsideDisables={true}>
|
||||
<div className={'kibanaCodeEditor__isFullScreen'}>{children}</div>
|
||||
</EuiFocusTrap>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
},
|
||||
[isFullScreen]
|
||||
);
|
||||
|
||||
return {
|
||||
FullScreenButton,
|
||||
FullScreenDisplay,
|
||||
onKeyDown,
|
||||
isFullScreen,
|
||||
setIsFullScreen,
|
||||
};
|
||||
};
|
||||
|
||||
const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string }) => {
|
||||
const showCopyButton = isCopyable && value;
|
||||
|
||||
const CopyButton = () => {
|
||||
if (!showCopyButton) return null;
|
||||
|
||||
return (
|
||||
<div className="euiCodeBlock__copyButton">
|
||||
<EuiI18n token="euiCodeBlock.copyButton" default="Copy">
|
||||
{(copyButton: string) => (
|
||||
<EuiCopy textToCopy={value}>
|
||||
{(copy) => (
|
||||
<EuiButtonIcon
|
||||
onClick={copy}
|
||||
iconType="copyClipboard"
|
||||
color="text"
|
||||
aria-label={copyButton}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
)}
|
||||
</EuiI18n>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return { showCopyButton, CopyButton };
|
||||
};
|
||||
|
||||
// React.lazy requires default export
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default CodeEditor;
|
||||
|
|
|
@ -27,4 +27,21 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
top: $euiSizeXS;
|
||||
right: $euiSize;
|
||||
position: absolute;
|
||||
z-index: 1000
|
||||
}
|
||||
|
||||
&__isFullScreen {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
.kibanaCodeEditor__controls {
|
||||
top: $euiSizeL;
|
||||
right: $euiSizeL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ export const CodeEditor = ({ ariaLabel, id, languageId, onChange, value }: Props
|
|||
options={{
|
||||
renderValidationDecorations: value ? 'on' : 'off',
|
||||
}}
|
||||
isCopyable={true}
|
||||
allowFullScreen={true}
|
||||
/>
|
||||
</div>
|
||||
</CodeEditorContainer>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue