Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotkeys in SQL Lab #4680

Merged
merged 3 commits into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions superset/assets/javascripts/SqlLab/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,11 @@ export function setDatabases(databases) {
}

export function addQueryEditor(queryEditor) {
const newQe = Object.assign({}, queryEditor, { id: shortid.generate() });
return { type: ADD_QUERY_EDITOR, queryEditor: newQe };
const newQueryEditor = {
...queryEditor,
id: shortid.generate(),
};
return { type: ADD_QUERY_EDITOR, queryEditor: newQueryEditor };
}

export function cloneQueryToNewTab(query) {
Expand Down
15 changes: 12 additions & 3 deletions superset/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ const sqlWords = sqlKeywords.map(s => ({
const propTypes = {
actions: PropTypes.object.isRequired,
onBlur: PropTypes.func,
onAltEnter: PropTypes.func,
sql: PropTypes.string.isRequired,
tables: PropTypes.array,
queryEditor: PropTypes.object.isRequired,
height: PropTypes.string,
hotkeys: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
descr: PropTypes.string.isRequired,
func: PropTypes.func.isRequired,
})),
};

const defaultProps = {
onBlur: () => {},
onAltEnter: () => {},
tables: [],
};

Expand Down Expand Up @@ -67,7 +70,6 @@ class AceEditorWrapper extends React.PureComponent {
}
onAltEnter() {
this.props.onBlur(this.state.sql);
this.props.onAltEnter();
}
onEditorLoad(editor) {
editor.commands.addCommand({
Expand All @@ -77,6 +79,13 @@ class AceEditorWrapper extends React.PureComponent {
this.onAltEnter();
},
});
this.props.hotkeys.forEach((keyConfig) => {
editor.commands.addCommand({
name: keyConfig.name,
bindKey: { win: keyConfig.key, mac: keyConfig.key },
exec: keyConfig.func,
});
});
editor.$blockScrolling = Infinity; // eslint-disable-line no-param-reassign
editor.selection.on('changeSelection', () => {
const selectedText = editor.getSelectedText();
Expand Down
64 changes: 48 additions & 16 deletions superset/assets/javascripts/SqlLab/components/SqlEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import SouthPane from './SouthPane';
import SaveQuery from './SaveQuery';
import ShareQuery from './ShareQuery';
import Timer from '../../components/Timer';
import Hotkeys from '../../components/Hotkeys';
import SqlEditorLeftBar from './SqlEditorLeftBar';
import AceEditorWrapper from './AceEditorWrapper';
import { STATE_BSSTYLE_MAP } from '../constants';
Expand All @@ -46,7 +47,6 @@ const defaultProps = {
hideLeftBar: false,
};


class SqlEditor extends React.PureComponent {
constructor(props) {
super(props);
Expand All @@ -57,6 +57,8 @@ class SqlEditor extends React.PureComponent {

this.onResize = this.onResize.bind(this);
this.throttledResize = throttle(this.onResize, 250);
this.runQuery = this.runQuery.bind(this);
this.stopQuery = this.stopQuery.bind(this);
}
componentWillMount() {
if (this.state.autorun) {
Expand Down Expand Up @@ -86,18 +88,39 @@ class SqlEditor extends React.PureComponent {
this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight);
}
}
getHotkeyConfig() {
return [
{
name: 'runQuery',
key: 'ctrl+r',
descr: 'Run query',
func: this.runQuery,
},
{
name: 'newTab',
key: 'ctrl+t',
descr: 'New tab',
func: () => {
this.props.actions.addQueryEditor({
...this.props.queryEditor,
title: t('Untitled Query'),
sql: '',
});
},
},
{
name: 'stopQuery',
key: 'ctrl+x',
descr: 'Stop query',
func: this.stopQuery,
},
];
}
setQueryEditorSql(sql) {
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
}
runQuery(runAsync = false) {
if (!this.props.queryEditor.sql) {
return;
}
let effectiveRunAsync = runAsync;
if (!this.props.database.allow_run_sync) {
effectiveRunAsync = true;
}
this.startQuery(effectiveRunAsync);
runQuery() {
this.startQuery(!this.props.database.allow_run_sync);
}
startQuery(runAsync = false, ctas = false) {
const qe = this.props.queryEditor;
Expand All @@ -116,7 +139,9 @@ class SqlEditor extends React.PureComponent {
this.props.actions.setActiveSouthPaneTab('Results');
}
stopQuery() {
this.props.actions.postStopQuery(this.props.latestQuery);
if (this.props.latestQuery && this.props.latestQuery.state === 'running') {
this.props.actions.postStopQuery(this.props.latestQuery);
}
}
createTableAs() {
this.startQuery(true, true);
Expand All @@ -128,7 +153,7 @@ class SqlEditor extends React.PureComponent {
const horizontalScrollbarHeight = 25;
return parseInt(this.props.getHeight(), 10) - horizontalScrollbarHeight;
}
renderEditorBottomBar() {
renderEditorBottomBar(hotkeys) {
let ctasControls;
if (this.props.database && this.props.database.allow_ctas) {
const ctasToolTip = t('Create table as with query results');
Expand Down Expand Up @@ -181,9 +206,9 @@ class SqlEditor extends React.PureComponent {
allowAsync={this.props.database ? this.props.database.allow_run_async : false}
dbId={qe.dbId}
queryState={this.props.latestQuery && this.props.latestQuery.state}
runQuery={this.runQuery.bind(this)}
runQuery={this.runQuery}
selectedText={qe.selectedText}
stopQuery={this.stopQuery.bind(this)}
stopQuery={this.stopQuery}
/>
</span>
<span className="m-r-5">
Expand All @@ -200,6 +225,12 @@ class SqlEditor extends React.PureComponent {
<ShareQuery queryEditor={qe} />
</span>
{ctasControls}
<span className="m-l-5">
<Hotkeys
header="Hotkeys"
hotkeys={hotkeys}
/>
</span>
</Form>
</div>
<div className="pull-right">
Expand All @@ -226,6 +257,7 @@ class SqlEditor extends React.PureComponent {
render() {
const height = this.sqlEditorHeight();
const defaultNorthHeight = this.props.queryEditor.height || 200;
const hotkeys = this.getHotkeyConfig();
return (
<div
className="SqlEditor"
Expand Down Expand Up @@ -271,12 +303,12 @@ class SqlEditor extends React.PureComponent {
actions={this.props.actions}
onBlur={this.setQueryEditorSql.bind(this)}
queryEditor={this.props.queryEditor}
onAltEnter={this.runQuery.bind(this)}
sql={this.props.queryEditor.sql}
tables={this.props.tables}
height={((this.state.editorPaneHeight || defaultNorthHeight) - 50) + 'px'}
hotkeys={hotkeys}
/>
{this.renderEditorBottomBar()}
{this.renderEditorBottomBar(hotkeys)}
</div>
</div>
<div ref="south">
Expand Down
53 changes: 53 additions & 0 deletions superset/assets/javascripts/components/Hotkeys.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { Table } from 'reactable';

import Mousetrap from 'mousetrap';

const propTypes = {
hotkeys: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
descr: PropTypes.string.isRequired,
func: PropTypes.func.isRequired,
})).isRequired,
header: PropTypes.string,
};

const defaultProps = {
hotkeys: [],
};

export default class Hotkeys extends React.PureComponent {
componentDidMount() {
this.props.hotkeys.forEach((keyConfig) => {
Mousetrap.bind([keyConfig.key], keyConfig.func);
});
}
renderPopover() {
return (
<Popover id="popover-hotkeys" title={this.props.header} style={{ width: '300px' }}>
<Table
className="table table-condensed"
data={this.props.hotkeys.map(keyConfig => ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!!!

Key: keyConfig.key,
Action: keyConfig.descr,
}))}
/>
</Popover>);
}
render() {
return (
<OverlayTrigger
overlay={this.renderPopover()}
trigger={['hover', 'focus']}
placement="top"
>
<i className="fa fa-keyboard-o fa-lg" />
</OverlayTrigger>
);
}
}

Hotkeys.propTypes = propTypes;
Hotkeys.defaultProps = defaultProps;