You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ju...@apache.org on 2023/10/12 15:13:44 UTC
[superset] branch master updated: feat(sqllab): Add keyboard shortcut helper (#25542)
This is an automated email from the ASF dual-hosted git repository.
justinpark pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 2dc5c5f53f feat(sqllab): Add keyboard shortcut helper (#25542)
2dc5c5f53f is described below
commit 2dc5c5f53f294f5eb0d5c122a39275ad2c91dd13
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Thu Oct 12 11:13:37 2023 -0400
feat(sqllab): Add keyboard shortcut helper (#25542)
---
.../SqlLab/components/AceEditorWrapper/index.tsx | 5 +-
.../KeyboardShortcutButton.test.tsx | 34 ++++++
.../components/KeyboardShortcutButton/index.tsx | 129 +++++++++++++++++++++
.../src/SqlLab/components/SqlEditor/index.tsx | 48 ++++++--
4 files changed, 202 insertions(+), 14 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
index 17c84a3664..3b6b2e67b6 100644
--- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
+++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
@@ -23,13 +23,14 @@ import { css, styled, usePrevious } from '@superset-ui/core';
import { queryEditorSetSelectedText } from 'src/SqlLab/actions/sqlLab';
import { FullSQLEditor as AceEditor } from 'src/components/AsyncAceEditor';
+import type { KeyboardShortcut } from 'src/SqlLab/components/KeyboardShortcutButton';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
import { useAnnotations } from './useAnnotations';
import { useKeywords } from './useKeywords';
type HotKey = {
- key: string;
- descr: string;
+ key: KeyboardShortcut;
+ descr?: string;
name: string;
func: (aceEditor: IAceEditor) => void;
};
diff --git a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx
new file mode 100644
index 0000000000..9582b9cab3
--- /dev/null
+++ b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { fireEvent, render } from 'spec/helpers/testing-library';
+import KeyboardShortcutButton, { KEY_MAP } from '.';
+
+test('renders shortcut description', () => {
+ const { getByText, getByRole } = render(
+ <KeyboardShortcutButton>Show shortcuts</KeyboardShortcutButton>,
+ );
+ fireEvent.click(getByRole('button'));
+ expect(getByText('Keyboard shortcuts')).toBeInTheDocument();
+ Object.keys(KEY_MAP)
+ .filter(key => Boolean(KEY_MAP[key]))
+ .forEach(key => {
+ expect(getByText(key)).toBeInTheDocument();
+ });
+});
diff --git a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx
new file mode 100644
index 0000000000..306e69e760
--- /dev/null
+++ b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx
@@ -0,0 +1,129 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { styled, t, css } from '@superset-ui/core';
+import ModalTrigger from 'src/components/ModalTrigger';
+import { detectOS } from 'src/utils/common';
+
+const userOS = detectOS();
+
+export enum KeyboardShortcut {
+ CTRL_R = 'ctrl+r',
+ CTRL_ENTER = 'ctrl+enter',
+ CTRL_SHIFT_ENTER = 'ctrl+shift+enter',
+ CTRL_P = 'ctrl+p',
+ CTRL_Q = 'ctrl+q',
+ CTRL_E = 'ctrl+e',
+ CTRL_T = 'ctrl+t',
+ CTRL_X = 'ctrl+x',
+ ALT_ENTER = 'alt+enter',
+ CMD_F = 'cmd+f',
+ CMD_OPT_F = 'cmd+opt+f',
+ CTRL_F = 'ctrl+f',
+ CTRL_H = 'ctrl+h',
+}
+
+export const KEY_MAP = {
+ [KeyboardShortcut.CTRL_R]: t('Run query'),
+ [KeyboardShortcut.CTRL_ENTER]: t('Run query'),
+ [KeyboardShortcut.ALT_ENTER]: t('Run query'),
+ [KeyboardShortcut.CTRL_SHIFT_ENTER]: t('Run current query'),
+ [KeyboardShortcut.CTRL_X]: userOS === 'MacOS' ? t('Stop query') : undefined,
+ [KeyboardShortcut.CTRL_E]: userOS !== 'MacOS' ? t('Stop query') : undefined,
+ [KeyboardShortcut.CTRL_Q]: userOS === 'Windows' ? t('New tab') : undefined,
+ [KeyboardShortcut.CTRL_T]: userOS !== 'Windows' ? t('New tab') : undefined,
+ [KeyboardShortcut.CTRL_P]: t('Previous Line'),
+ // default ace editor shortcuts
+ [KeyboardShortcut.CMD_F]: userOS === 'MacOS' ? t('Find') : undefined,
+ [KeyboardShortcut.CTRL_F]: userOS !== 'MacOS' ? t('Find') : undefined,
+ [KeyboardShortcut.CMD_OPT_F]: userOS === 'MacOS' ? t('Replace') : undefined,
+ [KeyboardShortcut.CTRL_H]: userOS !== 'MacOS' ? t('Replace') : undefined,
+};
+
+const KeyMapByCommand = Object.entries(KEY_MAP).reduce(
+ (acc, [shortcut, command]) => {
+ if (command) {
+ acc[command] = [...(acc[command] || []), shortcut];
+ }
+ return acc;
+ },
+ {} as Record<string, string[]>,
+);
+
+const ShortcutDescription = styled.span`
+ font-size: ${({ theme }) => theme.typography.sizes.m}px;
+ color: ${({ theme }) => theme.colors.text.help};
+ padding-left: ${({ theme }) => theme.gridUnit * 2}px;
+`;
+
+const ShortcutWrapper = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: ${({ theme }) => theme.gridUnit}px;
+ padding: ${({ theme }) => theme.gridUnit * 2}px;
+`;
+
+const ShortcutCode = styled.code`
+ font-size: ${({ theme }) => theme.typography.sizes.s}px;
+ color: ${({ theme }) => theme.colors.grayscale.dark1};
+ border-radius: ${({ theme }) => theme.borderRadius}px;
+ padding: ${({ theme }) => `${theme.gridUnit}px ${theme.gridUnit * 2}px`};
+`;
+
+const KeyboardShortcutButton: React.FC<{}> = ({ children }) => (
+ <ModalTrigger
+ modalTitle={t('Keyboard shortcuts')}
+ modalBody={
+ <div>
+ {Object.entries(KeyMapByCommand).map(([description, shortcuts]) => (
+ <div
+ key={description}
+ css={css`
+ display: table-row;
+ `}
+ >
+ <div
+ css={css`
+ display: table-cell;
+ max-width: 200px;
+ vertical-align: middle;
+ `}
+ >
+ <ShortcutDescription>{description}</ShortcutDescription>
+ </div>
+ <div
+ css={css`
+ display: table-cell;
+ `}
+ >
+ <ShortcutWrapper>
+ {shortcuts.map(shortcut => (
+ <ShortcutCode key={shortcut}>{shortcut}</ShortcutCode>
+ ))}
+ </ShortcutWrapper>
+ </div>
+ </div>
+ ))}
+ </div>
+ }
+ triggerNode={children}
+ />
+);
+
+export default KeyboardShortcutButton;
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
index 08ddf2b0fb..83bb80d997 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
@@ -103,6 +103,10 @@ import SqlEditorLeftBar, { ExtendedTable } from '../SqlEditorLeftBar';
import AceEditorWrapper from '../AceEditorWrapper';
import RunQueryActionButton from '../RunQueryActionButton';
import QueryLimitSelect from '../QueryLimitSelect';
+import KeyboardShortcutButton, {
+ KEY_MAP,
+ KeyboardShortcut,
+} from '../KeyboardShortcutButton';
const bootstrapData = getBootstrapData();
const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES;
@@ -114,6 +118,7 @@ const StyledToolbar = styled.div`
justify-content: space-between;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-top: 0;
+ column-gap: ${({ theme }) => theme.gridUnit}px;
form {
margin-block-end: 0;
@@ -333,8 +338,8 @@ const SqlEditor: React.FC<Props> = ({
return [
{
name: 'runQuery1',
- key: 'ctrl+r',
- descr: t('Run query'),
+ key: KeyboardShortcut.CTRL_R,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_R],
func: () => {
if (queryEditor.sql.trim() !== '') {
startQuery();
@@ -343,8 +348,8 @@ const SqlEditor: React.FC<Props> = ({
},
{
name: 'runQuery2',
- key: 'ctrl+enter',
- descr: t('Run query'),
+ key: KeyboardShortcut.CTRL_ENTER,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_ENTER],
func: () => {
if (queryEditor.sql.trim() !== '') {
startQuery();
@@ -353,16 +358,30 @@ const SqlEditor: React.FC<Props> = ({
},
{
name: 'newTab',
- key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t',
- descr: t('New tab'),
+ ...(userOS === 'Windows'
+ ? {
+ key: KeyboardShortcut.CTRL_Q,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_Q],
+ }
+ : {
+ key: KeyboardShortcut.CTRL_T,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_T],
+ }),
func: () => {
dispatch(addNewQueryEditor());
},
},
{
name: 'stopQuery',
- key: userOS === 'MacOS' ? 'ctrl+x' : 'ctrl+e',
- descr: t('Stop query'),
+ ...(userOS === 'MacOS'
+ ? {
+ key: KeyboardShortcut.CTRL_X,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_X],
+ }
+ : {
+ key: KeyboardShortcut.CTRL_E,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_E],
+ }),
func: stopQuery,
},
];
@@ -376,8 +395,8 @@ const SqlEditor: React.FC<Props> = ({
...getHotkeyConfig(),
{
name: 'runQuery3',
- key: 'ctrl+shift+enter',
- descr: t('Run current query'),
+ key: KeyboardShortcut.CTRL_SHIFT_ENTER,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_SHIFT_ENTER],
func: (editor: AceEditor['editor']) => {
if (!editor.getValue().trim()) {
return;
@@ -434,8 +453,8 @@ const SqlEditor: React.FC<Props> = ({
if (userOS === 'MacOS') {
base.push({
name: 'previousLine',
- key: 'ctrl+p',
- descr: t('Previous Line'),
+ key: KeyboardShortcut.CTRL_P,
+ descr: KEY_MAP[KeyboardShortcut.CTRL_P],
func: editor => {
editor.navigateUp();
},
@@ -617,6 +636,11 @@ const SqlEditor: React.FC<Props> = ({
/>
</Menu.Item>
)}
+ <Menu.Item>
+ <KeyboardShortcutButton>
+ {t('Keyboard shortcuts')}
+ </KeyboardShortcutButton>
+ </Menu.Item>
</Menu>
);
};