[Uptime monitor management] Add full screen/copy button ability in browser inline script editing (#124500)

This commit is contained in:
Shahzad 2022-02-10 09:54:10 +01:00 committed by GitHub
parent 157f8e1bbb
commit e93a651c1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 228 additions and 71 deletions

View file

@ -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}

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -38,6 +38,8 @@ export const CodeEditor = ({ ariaLabel, id, languageId, onChange, value }: Props
options={{
renderValidationDecorations: value ? 'on' : 'off',
}}
isCopyable={true}
allowFullScreen={true}
/>
</div>
</CodeEditorContainer>