[Console] Add option to disable keyboard shortcuts (#128887)

* Get editor instance to settings modal

* Add disable keyboard shortcuts feature

* Update editor.test.tsx

* Refactor

* Address comments

Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co>
This commit is contained in:
Muhammad Ibragimov 2022-04-13 14:33:12 +05:00 committed by GitHub
parent 04acd49496
commit 35d575c2ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 191 additions and 50 deletions

View file

@ -140,6 +140,13 @@ export function HelpPanel(props: Props) {
defaultMessage="Select the currently selected or the top most term in auto-complete menu"
/>
</dd>
<dt>Ctrl/Cmd + L</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.goToLineNumberDescription"
defaultMessage="Go to line number"
/>
</dd>
<dt>Esc</dt>
<dd>
<FormattedMessage

View file

@ -27,6 +27,8 @@ import {
} from '@elastic/eui';
import { DevToolsSettings } from '../../services';
import { unregisterCommands } from '../containers/editor/legacy/console_editor/keyboard_shortcuts';
import type { SenseEditor } from '../models';
export type AutocompleteOptions = 'fields' | 'indices' | 'templates';
@ -62,6 +64,7 @@ interface Props {
onClose: () => void;
refreshAutocompleteSettings: (selectedSettings: DevToolsSettings['autocomplete']) => void;
settings: DevToolsSettings;
editorInstance: SenseEditor | null;
}
export function DevToolsSettingsModal(props: Props) {
@ -74,7 +77,10 @@ export function DevToolsSettingsModal(props: Props) {
const [polling, setPolling] = useState(props.settings.polling);
const [pollInterval, setPollInterval] = useState(props.settings.pollInterval);
const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes);
const [historyDisabled, setHistoryDisabled] = useState(props.settings.historyDisabled);
const [isHistoryDisabled, setIsHistoryDisabled] = useState(props.settings.isHistoryDisabled);
const [isKeyboardShortcutsDisabled, setIsKeyboardShortcutsDisabled] = useState(
props.settings.isKeyboardShortcutsDisabled
);
const autoCompleteCheckboxes = [
{
@ -134,7 +140,8 @@ export function DevToolsSettingsModal(props: Props) {
polling,
pollInterval,
tripleQuotes,
historyDisabled,
isHistoryDisabled,
isKeyboardShortcutsDisabled,
});
}
@ -145,6 +152,21 @@ export function DevToolsSettingsModal(props: Props) {
setPollInterval(sanitizedValue);
}, []);
const toggleKeyboardShortcuts = useCallback(
(isDisabled: boolean) => {
if (props.editorInstance) {
unregisterCommands(props.editorInstance);
setIsKeyboardShortcutsDisabled(isDisabled);
}
},
[props.editorInstance]
);
const toggleSavingToHistory = useCallback(
(isDisabled: boolean) => setIsHistoryDisabled(isDisabled),
[]
);
// It only makes sense to show polling options if the user needs to fetch any data.
const pollingFields =
fields || indices || templates ? (
@ -160,7 +182,7 @@ export function DevToolsSettingsModal(props: Props) {
<FormattedMessage
id="console.settingsPage.refreshingDataDescription"
defaultMessage="Console refreshes autocomplete suggestions by querying Elasticsearch.
Less frequent refresh is recommended to reduce bandwith costs."
Less frequent refresh is recommended to reduce bandwidth costs."
/>
}
>
@ -267,15 +289,34 @@ export function DevToolsSettingsModal(props: Props) {
}
>
<EuiSwitch
checked={historyDisabled}
id="historyDisabled"
checked={isHistoryDisabled}
label={
<FormattedMessage
defaultMessage="Disable saving requests to history"
id="console.settingsPage.savingRequestsToHistoryMessage"
/>
}
onChange={(e) => setHistoryDisabled(e.target.checked)}
onChange={(e) => toggleSavingToHistory(e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="console.settingsPage.keyboardShortcutsLabel"
defaultMessage="Keyboard shortcuts"
/>
}
>
<EuiSwitch
checked={isKeyboardShortcutsDisabled}
label={
<FormattedMessage
defaultMessage="Disable keyboard shortcuts"
id="console.settingsPage.disableKeyboardShortcutsMessage"
/>
}
onChange={(e) => toggleKeyboardShortcuts(e.target.checked)}
/>
</EuiFormRow>

View file

@ -15,15 +15,17 @@ import { Panel, PanelsContainer } from '../../containers';
import { Editor as EditorUI, EditorOutput } from './legacy/console_editor';
import { StorageKeys } from '../../../services';
import { useEditorReadContext, useServicesContext, useRequestReadContext } from '../../contexts';
import type { SenseEditor } from '../../models';
const INITIAL_PANEL_WIDTH = 50;
const PANEL_MIN_WIDTH = '100px';
interface Props {
loading: boolean;
setEditorInstance: (instance: SenseEditor) => void;
}
export const Editor = memo(({ loading }: Props) => {
export const Editor = memo(({ loading, setEditorInstance }: Props) => {
const {
services: { storage },
} = useServicesContext();
@ -61,7 +63,10 @@ export const Editor = memo(({ loading }: Props) => {
{loading ? (
<EditorContentSpinner />
) : (
<EditorUI initialTextValue={currentTextObject.text} />
<EditorUI
initialTextValue={currentTextObject.text}
setEditorInstance={setEditorInstance}
/>
)}
</Panel>
<Panel

View file

@ -41,7 +41,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => {
<ServicesContextProvider value={mockedAppContextValue}>
<RequestContextProvider>
<EditorContextProvider settings={{} as unknown as DevToolsSettings}>
<Editor initialTextValue="" />
<Editor initialTextValue="" setEditorInstance={() => {}} />
</EditorContextProvider>
</RequestContextProvider>
</ServicesContextProvider>

View file

@ -34,11 +34,13 @@ import { autoIndent, getDocumentation } from '../console_menu_actions';
import { subscribeResizeChecker } from '../subscribe_console_resize_checker';
import { applyCurrentSettings } from './apply_editor_settings';
import { registerCommands } from './keyboard_shortcuts';
import type { SenseEditor } from '../../../../models/sense_editor';
const { useUIAceKeyboardMode } = ace;
export interface EditorProps {
initialTextValue: string;
setEditorInstance: (instance: SenseEditor) => void;
}
interface QueryParams {
@ -62,7 +64,7 @@ const DEFAULT_INPUT_VALUE = `GET _search
const inputId = 'ConAppInputTextarea';
function EditorUI({ initialTextValue }: EditorProps) {
function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
const {
services: { history, notifications, settings: settingsService, esHostService, http },
docLinkVersion,
@ -225,12 +227,22 @@ function EditorUI({ initialTextValue }: EditorProps) {
}, [settings]);
useEffect(() => {
registerCommands({
senseEditor: editorInstanceRef.current!,
sendCurrentRequestToES,
openDocumentation,
});
}, [sendCurrentRequestToES, openDocumentation]);
const { isKeyboardShortcutsDisabled } = settings;
if (!isKeyboardShortcutsDisabled) {
registerCommands({
senseEditor: editorInstanceRef.current!,
sendCurrentRequestToES,
openDocumentation,
});
}
}, [sendCurrentRequestToES, openDocumentation, settings]);
useEffect(() => {
const { current: editor } = editorInstanceRef;
if (editor) {
setEditorInstance(editor);
}
}, [setEditorInstance]);
return (
<div style={abs} data-test-subj="console-application" className="conApp">

View file

@ -15,6 +15,15 @@ interface Actions {
openDocumentation: () => void;
}
const COMMANDS = {
SEND_TO_ELASTICSEARCH: 'send to Elasticsearch',
OPEN_DOCUMENTATION: 'open documentation',
AUTO_INDENT_REQUEST: 'auto indent request',
MOVE_TO_PREVIOUS_REQUEST: 'move to previous request start or end',
MOVE_TO_NEXT_REQUEST: 'move to next request start or end',
GO_TO_LINE: 'gotoline',
};
export function registerCommands({
senseEditor,
sendCurrentRequestToES,
@ -28,12 +37,14 @@ export function registerCommands({
coreEditor.registerKeyboardShortcut({
keys: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
name: 'send to Elasticsearch',
fn: () => sendCurrentRequestToES(),
name: COMMANDS.SEND_TO_ELASTICSEARCH,
fn: () => {
sendCurrentRequestToES();
},
});
coreEditor.registerKeyboardShortcut({
name: 'open documentation',
name: COMMANDS.OPEN_DOCUMENTATION,
keys: { win: 'Ctrl-/', mac: 'Command-/' },
fn: () => {
openDocumentation();
@ -41,7 +52,7 @@ export function registerCommands({
});
coreEditor.registerKeyboardShortcut({
name: 'auto indent request',
name: COMMANDS.AUTO_INDENT_REQUEST,
keys: { win: 'Ctrl-I', mac: 'Command-I' },
fn: () => {
throttledAutoIndent();
@ -49,7 +60,7 @@ export function registerCommands({
});
coreEditor.registerKeyboardShortcut({
name: 'move to previous request start or end',
name: COMMANDS.MOVE_TO_PREVIOUS_REQUEST,
keys: { win: 'Ctrl-Up', mac: 'Command-Up' },
fn: () => {
senseEditor.moveToPreviousRequestEdge();
@ -57,10 +68,28 @@ export function registerCommands({
});
coreEditor.registerKeyboardShortcut({
name: 'move to next request start or end',
name: COMMANDS.MOVE_TO_NEXT_REQUEST,
keys: { win: 'Ctrl-Down', mac: 'Command-Down' },
fn: () => {
senseEditor.moveToNextRequestEdge(false);
},
});
coreEditor.registerKeyboardShortcut({
name: COMMANDS.GO_TO_LINE,
keys: { win: 'Ctrl-L', mac: 'Command-L' },
fn: (editor) => {
const line = parseInt(prompt('Enter line number') ?? '', 10);
if (!isNaN(line)) {
editor.gotoLine(line);
}
},
});
}
export function unregisterCommands(senseEditor: SenseEditor) {
const coreEditor = senseEditor.getCoreEditor();
Object.values(COMMANDS).forEach((command) => {
coreEditor.unregisterKeyboardShortcut(command);
});
}

View file

@ -25,6 +25,7 @@ import { useServicesContext, useEditorReadContext, useRequestReadContext } from
import { useDataInit } from '../../hooks';
import { getTopNavConfig } from './get_top_nav';
import type { SenseEditor } from '../../models/sense_editor';
export function Main() {
const {
@ -46,6 +47,8 @@ export function Main() {
const [showSettings, setShowSettings] = useState(false);
const [showHelp, setShowHelp] = useState(false);
const [editorInstance, setEditorInstance] = useState<SenseEditor | null>(null);
const renderConsoleHistory = () => {
return editorsReady ? <ConsoleHistory close={() => setShowHistory(false)} /> : null;
};
@ -108,7 +111,7 @@ export function Main() {
</EuiFlexItem>
{showingHistory ? <EuiFlexItem grow={false}>{renderConsoleHistory()}</EuiFlexItem> : null}
<EuiFlexItem>
<Editor loading={!done} />
<Editor loading={!done} setEditorInstance={setEditorInstance} />
</EuiFlexItem>
</EuiFlexGroup>
@ -121,7 +124,9 @@ export function Main() {
/>
) : null}
{showSettings ? <Settings onClose={() => setShowSettings(false)} /> : null}
{showSettings ? (
<Settings onClose={() => setShowSettings(false)} editorInstance={editorInstance} />
) : null}
{showHelp ? <HelpPanel onClose={() => setShowHelp(false)} /> : null}
</div>

View file

@ -15,6 +15,7 @@ import { AutocompleteOptions, DevToolsSettingsModal } from '../components';
import { retrieveAutoCompleteInfo } from '../../lib/mappings/mappings';
import { useServicesContext, useEditorActionContext } from '../contexts';
import { DevToolsSettings, Settings as SettingsService } from '../../services';
import type { SenseEditor } from '../models';
const getAutocompleteDiff = (
newSettings: DevToolsSettings,
@ -70,9 +71,10 @@ const fetchAutocompleteSettingsIfNeeded = (
export interface Props {
onClose: () => void;
editorInstance: SenseEditor | null;
}
export function Settings({ onClose }: Props) {
export function Settings({ onClose, editorInstance }: Props) {
const {
services: { settings, http },
} = useServicesContext();
@ -102,6 +104,7 @@ export function Settings({ onClose }: Props) {
refreshAutocompleteSettings(http, settings, selectedSettings)
}
settings={settings.toJSON()}
editorInstance={editorInstance}
/>
);
}

View file

@ -49,9 +49,9 @@ export const useSendCurrentRequestToES = () => {
const results = await sendRequestToES({ http, requests });
let saveToHistoryError: undefined | Error;
const { historyDisabled } = settings.toJSON();
const { isHistoryDisabled } = settings.toJSON();
if (!historyDisabled) {
if (!isHistoryDisabled) {
results.forEach(({ request: { path, method, data } }) => {
try {
history.addToHistory(path, method, data);
@ -81,7 +81,7 @@ export const useSendCurrentRequestToES = () => {
notifications.toasts.remove(toast);
},
onDisableSavingToHistory: () => {
settings.setHistoryDisabled(true);
settings.setIsHistoryDisabled(true);
notifications.toasts.remove(toast);
},
}),

View file

@ -297,6 +297,11 @@ export class LegacyCoreEditor implements CoreEditor {
});
}
unregisterKeyboardShortcut(command: string) {
// @ts-ignore
this.editor.commands.removeCommand(command);
}
legacyUpdateUI(range: Range) {
if (!this.$actions) {
return;

View file

@ -15,7 +15,8 @@ export const DEFAULT_SETTINGS = Object.freeze({
tripleQuotes: true,
wrapMode: true,
autocomplete: Object.freeze({ fields: true, indices: true, templates: true, dataStreams: true }),
historyDisabled: false,
isHistoryDisabled: false,
isKeyboardShortcutsDisabled: false,
});
export interface DevToolsSettings {
@ -30,72 +31,96 @@ export interface DevToolsSettings {
polling: boolean;
pollInterval: number;
tripleQuotes: boolean;
historyDisabled: boolean;
isHistoryDisabled: boolean;
isKeyboardShortcutsDisabled: boolean;
}
enum SettingKeys {
FONT_SIZE = 'font_size',
WRAP_MODE = 'wrap_mode',
TRIPLE_QUOTES = 'triple_quotes',
AUTOCOMPLETE_SETTINGS = 'autocomplete_settings',
CONSOLE_POLLING = 'console_polling',
POLL_INTERVAL = 'poll_interval',
IS_HISTORY_DISABLED = 'is_history_disabled',
IS_KEYBOARD_SHORTCUTS_DISABLED = 'is_keyboard_shortcuts_disabled',
}
export class Settings {
constructor(private readonly storage: Storage) {}
getFontSize() {
return this.storage.get('font_size', DEFAULT_SETTINGS.fontSize);
return this.storage.get(SettingKeys.FONT_SIZE, DEFAULT_SETTINGS.fontSize);
}
setFontSize(size: number) {
this.storage.set('font_size', size);
this.storage.set(SettingKeys.FONT_SIZE, size);
return true;
}
getWrapMode() {
return this.storage.get('wrap_mode', DEFAULT_SETTINGS.wrapMode);
return this.storage.get(SettingKeys.WRAP_MODE, DEFAULT_SETTINGS.wrapMode);
}
setWrapMode(mode: boolean) {
this.storage.set('wrap_mode', mode);
this.storage.set(SettingKeys.WRAP_MODE, mode);
return true;
}
setTripleQuotes(tripleQuotes: boolean) {
this.storage.set('triple_quotes', tripleQuotes);
this.storage.set(SettingKeys.TRIPLE_QUOTES, tripleQuotes);
return true;
}
getTripleQuotes() {
return this.storage.get('triple_quotes', DEFAULT_SETTINGS.tripleQuotes);
return this.storage.get(SettingKeys.TRIPLE_QUOTES, DEFAULT_SETTINGS.tripleQuotes);
}
getAutocomplete() {
return this.storage.get('autocomplete_settings', DEFAULT_SETTINGS.autocomplete);
return this.storage.get(SettingKeys.AUTOCOMPLETE_SETTINGS, DEFAULT_SETTINGS.autocomplete);
}
setAutocomplete(settings: object) {
this.storage.set('autocomplete_settings', settings);
this.storage.set(SettingKeys.AUTOCOMPLETE_SETTINGS, settings);
return true;
}
getPolling() {
return this.storage.get('console_polling', DEFAULT_SETTINGS.polling);
return this.storage.get(SettingKeys.CONSOLE_POLLING, DEFAULT_SETTINGS.polling);
}
setPolling(polling: boolean) {
this.storage.set('console_polling', polling);
this.storage.set(SettingKeys.CONSOLE_POLLING, polling);
return true;
}
setHistoryDisabled(disable: boolean) {
this.storage.set('disable_history', disable);
setIsHistoryDisabled(isDisabled: boolean) {
this.storage.set(SettingKeys.IS_HISTORY_DISABLED, isDisabled);
return true;
}
getHistoryDisabled() {
return this.storage.get('disable_history', DEFAULT_SETTINGS.historyDisabled);
getIsHistoryDisabled() {
return this.storage.get(SettingKeys.IS_HISTORY_DISABLED, DEFAULT_SETTINGS.isHistoryDisabled);
}
setPollInterval(interval: number) {
this.storage.set('poll_interval', interval);
this.storage.set(SettingKeys.POLL_INTERVAL, interval);
}
getPollInterval() {
return this.storage.get('poll_interval', DEFAULT_SETTINGS.pollInterval);
return this.storage.get(SettingKeys.POLL_INTERVAL, DEFAULT_SETTINGS.pollInterval);
}
setIsKeyboardShortcutsDisabled(disable: boolean) {
this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, disable);
return true;
}
getIsKeyboardShortcutsDisabled() {
return this.storage.get(
SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED,
DEFAULT_SETTINGS.isKeyboardShortcutsDisabled
);
}
toJSON(): DevToolsSettings {
@ -106,7 +131,8 @@ export class Settings {
fontSize: parseFloat(this.getFontSize()),
polling: Boolean(this.getPolling()),
pollInterval: this.getPollInterval(),
historyDisabled: Boolean(this.getHistoryDisabled()),
isHistoryDisabled: Boolean(this.getIsHistoryDisabled()),
isKeyboardShortcutsDisabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
};
}
@ -117,7 +143,8 @@ export class Settings {
autocomplete,
polling,
pollInterval,
historyDisabled,
isHistoryDisabled,
isKeyboardShortcutsDisabled,
}: DevToolsSettings) {
this.setFontSize(fontSize);
this.setWrapMode(wrapMode);
@ -125,7 +152,8 @@ export class Settings {
this.setAutocomplete(autocomplete);
this.setPolling(polling);
this.setPollInterval(pollInterval);
this.setHistoryDisabled(historyDisabled);
this.setIsHistoryDisabled(isHistoryDisabled);
this.setIsKeyboardShortcutsDisabled(isKeyboardShortcutsDisabled);
}
}

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import type { Editor } from 'brace';
import { TokensProvider } from './tokens_provider';
import { Token } from './token';
@ -252,10 +253,15 @@ export interface CoreEditor {
*/
registerKeyboardShortcut(opts: {
keys: string | { win?: string; mac?: string };
fn: () => void;
fn: (editor: Editor) => void;
name: string;
}): void;
/**
* Unregister a keyboard shortcut and provide a command name
*/
unregisterKeyboardShortcut(command: string): void;
/**
* Register a completions function that will be called when the editor
* detects a change