Skip to content

Commit

Permalink
[CodeEditor/UrlDrilldown] Add fitToContent support, autoresize the …
Browse files Browse the repository at this point in the history
…url template editor (#175561)

## Summary

This PR fixes the paper cut where the URL template editor in URL
drilldown is unusably small. It now can expand as you type longer URLs
fix #132513

The input box now expands from 5 to 15 lines.
  • Loading branch information
Dosant authored Feb 6, 2024
1 parent 43d93ba commit 86e8bc1
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 34 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,6 @@
"react-popper-tooltip": "^3.1.1",
"react-redux": "^7.2.8",
"react-resizable": "^3.0.4",
"react-resize-detector": "^7.1.1",
"react-reverse-portal": "^2.1.0",
"react-router": "^5.3.4",
"react-router-config": "^5.1.1",
Expand Down
1 change: 0 additions & 1 deletion packages/shared-ux/code_editor/impl/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ BUNDLER_DEPS = [
"@npm//react",
"@npm//tslib",
"@npm//react-monaco-editor",
"@npm//react-resize-detector",
]

js_library(
Expand Down
46 changes: 44 additions & 2 deletions packages/shared-ux/code_editor/impl/code_editor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React from 'react';
import React, { useState } from 'react';

import { action } from '@storybook/addon-actions';
import { monaco as monacoEditor } from '@kbn/monaco';
Expand All @@ -32,7 +32,13 @@ const argTypes = mock.getArgumentTypes();

export const Basic = (params: CodeEditorStorybookParams) => {
return (
<CodeEditor {...params} languageId="plainText" onChange={action('on change')} value="Hello!" />
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={200}
/>
);
};

Expand Down Expand Up @@ -199,3 +205,39 @@ export const HoverProvider = () => {
</div>
);
};

export const AutomaticResize = (params: CodeEditorStorybookParams) => {
return (
<div style={{ height: `calc(100vh - 30px)` }}>
<CodeEditor
{...params}
languageId="plainText"
onChange={action('on change')}
value="Hello!"
height={'100%'}
options={{ automaticLayout: true }}
/>
</div>
);
};

AutomaticResize.argTypes = argTypes;

export const FitToContent = (params: CodeEditorStorybookParams) => {
const [value, setValue] = useState('hello');
return (
<CodeEditor
{...params}
languageId="plainText"
onChange={(newValue) => {
setValue(newValue);
action('on change');
}}
value={value}
fitToContent={{ minLines: 3, maxLines: 5 }}
options={{ automaticLayout: true }}
/>
);
};

FitToContent.argTypes = argTypes;
78 changes: 57 additions & 21 deletions packages/shared-ux/code_editor/impl/code_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

import React, { useState, useRef, useCallback, useMemo, useEffect, KeyboardEvent } from 'react';
import { useResizeDetector } from 'react-resize-detector';
import ReactMonacoEditor, {
type MonacoEditorProps as ReactMonacoEditorProps,
} from 'react-monaco-editor';
Expand Down Expand Up @@ -140,6 +139,15 @@ export interface CodeEditorProps {
* Alternate text to display, when an attempt is made to edit read only content. (Defaults to "Cannot edit in read-only editor")
*/
readOnlyMessage?: string;

/**
* Enables the editor to grow vertically to fit its content.
* This option overrides the `height` option.
*/
fitToContent?: {
minLines?: number;
maxLines?: number;
};
}

export const CodeEditor: React.FC<CodeEditorProps> = ({
Expand Down Expand Up @@ -168,6 +176,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
readOnlyMessage = i18n.translate('sharedUXPackages.codeEditor.readOnlyMessage', {
defaultMessage: 'Cannot edit in read-only editor',
}),
fitToContent,
}) => {
const { colorMode, euiTheme } = useEuiTheme();
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
Expand All @@ -189,29 +198,18 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({

const isReadOnly = options?.readOnly ?? false;

const _editor = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const [_editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
const _placeholderWidget = useRef<PlaceholderWidget | null>(null);
const isSuggestionMenuOpen = useRef(false);
const editorHint = useRef<HTMLDivElement>(null);
const textboxMutationObserver = useRef<MutationObserver | null>(null);

const [isHintActive, setIsHintActive] = useState(true);

const _updateDimensions = useCallback(() => {
_editor.current?.layout();
}, []);

useResizeDetector({
handleWidth: true,
handleHeight: true,
onResize: _updateDimensions,
refreshMode: 'debounce',
});

const startEditing = useCallback(() => {
setIsHintActive(false);
_editor.current?.focus();
}, []);
_editor?.focus();
}, [_editor]);

const stopEditing = useCallback(() => {
setIsHintActive(true);
Expand Down Expand Up @@ -391,8 +389,6 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({

remeasureFonts();

_editor.current = editor;

const textbox = editor.getDomNode()?.getElementsByTagName('textarea')[0];
if (textbox) {
// Make sure the textarea is not directly accessible with TAB
Expand Down Expand Up @@ -435,6 +431,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
}

editorDidMount?.(editor);
setEditor(editor);
},
[editorDidMount, onBlurMonaco, onKeydownMonaco, readOnlyMessage]
);
Expand All @@ -454,16 +451,18 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
}, []);

useEffect(() => {
if (placeholder && !value && _editor.current) {
if (placeholder && !value && _editor) {
// Mounts editor inside constructor
_placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor.current);
_placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor);
}

return () => {
_placeholderWidget.current?.dispose();
_placeholderWidget.current = null;
};
}, [placeholder, value, euiTheme]);
}, [placeholder, value, euiTheme, _editor]);

useFitToContent({ editor: _editor, fitToContent, isFullScreen });

const { CopyButton } = useCopy({ isCopyable, value });

Expand Down Expand Up @@ -512,7 +511,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
value={value}
onChange={onChange}
width={isFullScreen ? '100vw' : width}
height={isFullScreen ? '100vh' : height}
height={isFullScreen ? '100vh' : fitToContent ? undefined : height}
editorWillMount={_editorWillMount}
editorDidMount={_editorDidMount}
editorWillUnmount={_editorWillUnmount}
Expand Down Expand Up @@ -640,3 +639,40 @@ const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string })

return { showCopyButton, CopyButton };
};

const useFitToContent = ({
editor,
fitToContent,
isFullScreen,
}: {
editor: monaco.editor.IStandaloneCodeEditor | null;
isFullScreen: boolean;
fitToContent?: { minLines?: number; maxLines?: number };
}) => {
const isFitToContent = !!fitToContent;
const minLines = fitToContent?.minLines;
const maxLines = fitToContent?.maxLines;
useEffect(() => {
if (!editor) return;
if (isFullScreen) return;
if (!isFitToContent) return;

const updateHeight = () => {
const contentHeight = editor.getContentHeight();
const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
const minHeight = (minLines ?? 1) * lineHeight;
let maxHeight = maxLines ? maxLines * lineHeight : contentHeight;
maxHeight = Math.max(minHeight, maxHeight);
editor.layout({
height: Math.min(maxHeight, Math.max(minHeight, contentHeight)),
width: editor.getLayoutInfo().width,
});
};
updateHeight();
const disposable = editor.onDidContentSizeChange(updateHeight);
return () => {
disposable.dispose();
editor.layout(); // reset the layout that was controlled by the fitToContent
};
}, [editor, isFitToContent, minLines, maxLines, isFullScreen]);
};
12 changes: 11 additions & 1 deletion packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ export const MockedMonacoEditor = ({
className?: string;
['data-test-subj']?: string;
}) => {
editorWillMount?.(monaco);
useComponentWillMount(() => {
editorWillMount?.(monaco);
});

useEffect(() => {
editorDidMount?.(
Expand All @@ -133,3 +135,11 @@ export const MockedMonacoEditor = ({
</div>
);
};

const useComponentWillMount = (cb: Function) => {
const willMount = React.useRef(true);

if (willMount.current) cb();

willMount.current = false;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.urlTemplateEditor__container {
.monaco-editor .lines-content.monaco-editor-background {
margin: $euiSizeS;
margin: 0 $euiSizeS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface UrlTemplateEditorVariable {
export interface UrlTemplateEditorProps {
value: string;
height?: CodeEditorProps['height'];
fitToContent?: CodeEditorProps['fitToContent'];
variables?: UrlTemplateEditorVariable[];
onChange: CodeEditorProps['onChange'];
onEditor?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
Expand All @@ -31,6 +32,7 @@ export interface UrlTemplateEditorProps {

export const UrlTemplateEditor: React.FC<UrlTemplateEditorProps> = ({
height = 105,
fitToContent,
value,
variables,
onChange,
Expand Down Expand Up @@ -127,6 +129,7 @@ export const UrlTemplateEditor: React.FC<UrlTemplateEditorProps> = ({
<Editor
languageId={HandlebarsLang}
height={height}
fitToContent={fitToContent}
value={value}
onChange={onChange}
editorDidMount={handleEditor}
Expand All @@ -152,6 +155,10 @@ export const UrlTemplateEditor: React.FC<UrlTemplateEditorProps> = ({
},
wordWrap: 'on',
wrappingIndent: 'none',
automaticLayout: true,
scrollBeyondLastLine: false,
overviewRulerLanes: 0,
padding: { top: 8, bottom: 8 },
}}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const UrlDrilldownCollectConfig: React.FC<UrlDrilldownCollectConfigProps>
labelAppend={variablesDropdown}
>
<UrlTemplateEditor
fitToContent={{ minLines: 5, maxLines: 15 }}
variables={variables}
value={urlTemplate}
placeholder={exampleUrl}
Expand Down
7 changes: 0 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -26016,13 +26016,6 @@ react-resizable@^3.0.4:
prop-types "15.x"
react-draggable "^4.0.3"

react-resize-detector@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.1.tgz#18d5b84909d5ab13abe0a68ddf0fb8e80c553dfc"
integrity sha512-rU54VTstNzFLZAmMNHqt8xINjDWP7SQR05A2HUW0OGvl4vcrXzgaxrrqAY5tZMfkLkoYm5u0i0qGqCjdc2jyAA==
dependencies:
lodash "^4.17.21"

react-reverse-portal@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-reverse-portal/-/react-reverse-portal-2.1.0.tgz#3c572e1c0d9e49b8febf4bf2fd43b9819ce6f508"
Expand Down

0 comments on commit 86e8bc1

Please sign in to comment.