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

[Canvas] Adds doc links and keyboard shortcut cheatsheat to help menu #31335

Merged
merged 11 commits into from
Feb 21, 2019
9 changes: 9 additions & 0 deletions x-pack/plugins/canvas/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'ui/autoload/all';
import chrome from 'ui/chrome';
import './angular/config';
import './angular/services';
import React from 'react';
import ReactDOM from 'react-dom';
import { CanvasRootController } from './angular/controllers';

// Import the uiExports that the application uses
Expand All @@ -24,5 +26,12 @@ import './lib/load_expression_types';
import './lib/load_transitions';
import 'uiExports/canvas';

import { HelpMenu } from './components/help_menu/help_menu';

// load the application
chrome.setRootController('canvas', CanvasRootController);

// add Canvas docs to help menu in global nav
chrome.helpExtension.set(domNode => {
ReactDOM.render(<HelpMenu />, domNode);
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ export class FullscreenControl extends React.PureComponent {

return (
<span>
<Shortcuts name="EDITOR" handler={keyHandler} targetNodeSelector="body" global isolate />
<Shortcuts
name="PRESENTATION"
handler={keyHandler}
targetNodeSelector="body"
global
isolate
/>
{children({ isFullscreen, toggleFullscreen: this.toggleFullscreen })}
</span>
);
Expand Down
48 changes: 48 additions & 0 deletions x-pack/plugins/canvas/public/components/help_menu/help_menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, PureComponent } from 'react';
import { EuiButton, EuiHorizontalRule, EuiText, EuiSpacer, EuiPortal } from '@elastic/eui';
import { documentationLinks } from '../../lib/documentation_links';
import { KeyboardShortcutsDoc } from '../keyboard_shortcuts_doc';

export class HelpMenu extends PureComponent {
state = { isFlyoutVisible: false };

showFlyout = () => {
this.setState({ isFlyoutVisible: true });
};

hideFlyout = () => {
this.setState({ isFlyoutVisible: false });
};

render() {
return (
<Fragment>
<EuiHorizontalRule margin="none" />
<EuiSpacer />
<EuiText size="s">
<p>For Canvas specific information</p>
</EuiText>
<EuiSpacer />
<EuiButton fill iconType="popout" href={documentationLinks.canvas} target="_blank">
Canvas documentation
</EuiButton>
<EuiSpacer />
<EuiButton onClick={this.showFlyout} target="_blank">
Keyboard shortcuts
</EuiButton>

{this.state.isFlyoutVisible && (
<EuiPortal>
<KeyboardShortcutsDoc onClose={this.hideFlyout} />
</EuiPortal>
)}
</Fragment>
);
}
}
7 changes: 7 additions & 0 deletions x-pack/plugins/canvas/public/components/help_menu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { HelpMenu } from './help_menu';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { KeyboardShortcutsDoc } from './keyboard_shortcuts_doc';
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiDescriptionList,
EuiHorizontalRule,
EuiCode,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { keymap } from '../../lib/keymap';
import { getClientPlatform } from '../../lib/get_client_platform';
import { getId } from '../../lib/get_id';

const getPrettyShortcut = shortcut => {
if (!shortcut) {
return '';
}

let result = shortcut.replace(/command/i, '⌘');
result = result.replace(/option/i, '⌥');
result = result.replace(/left/i, '←');
result = result.replace(/right/i, '→');
result = result.replace(/up/i, '↑');
result = result.replace(/down/i, '↓');

return (
<span key={getId('span')}>
{result
.split(/(\+)/g) //splits the array by '+' and keeps the '+'s as elements in the array
.map(key => (key === '+' ? ` ${key} ` : <EuiCode key={getId('shortcut')}>{key}</EuiCode>))}
</span>
);
};

const getDescriptionListItems = shortcuts =>
Object.values(shortcuts).map(shortcutKeyMap => {
const os = getClientPlatform();
const osShortcuts = shortcutKeyMap[os];
return {
title: shortcutKeyMap.help,
description: osShortcuts.reduce((acc, shortcut, i) => {
if (i !== 0) {
acc.push(' or ');
}
acc.push(getPrettyShortcut(shortcut));
return acc;
}, []),
};
});

export const KeyboardShortcutsDoc = props => (
<EuiFlyout closeButtonAriaLabel="Closes keyboard shortcuts reference" size="s" {...props}>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2>Keyboard Shortcuts</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{Object.values(keymap).map(namespace => {
const { displayName, ...shortcuts } = namespace;
return (
<div key={getId('shortcuts')} className="canvasKeyboardShortcut">
<EuiTitle size="xs">
<h4>{displayName}</h4>
</EuiTitle>
<EuiHorizontalRule margin="s" />
<EuiDescriptionList
textStyle="reverse"
type="column"
listItems={getDescriptionListItems(shortcuts)}
compressed
/>
<EuiSpacer />
</div>
);
})}
</EuiFlyoutBody>
</EuiFlyout>
);

KeyboardShortcutsDoc.propTypes = {
onClose: PropTypes.func.isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const WorkpadHeader = ({
<EuiFlexItem grow={false}>
<FullscreenControl>
{({ toggleFullscreen }) => (
<EuiToolTip position="bottom" content="Toggle fullscreen mode">
<EuiToolTip position="bottom" content="Enter fullscreen mode">
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

<EuiButtonIcon
iconType="fullScreen"
aria-label="View fullscreen"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiBetaBadge,
EuiLink,
} from '@elastic/eui';
import { WorkpadLoader } from '../workpad_loader';
import { WorkpadTemplates } from '../workpad_templates';
import { documentationLinks } from '../../lib/documentation_links';

export const WorkpadManager = ({ onClose }) => {
const tabs = [
Expand Down Expand Up @@ -57,11 +55,6 @@ export const WorkpadManager = ({ onClose }) => {
tooltipContent="Canvas is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo."
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink href={documentationLinks.canvas} target="_blank">
Docs
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalHeader>
<EuiModalBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,70 +124,10 @@ const handleMouseDown = (commit, e, isEditable) => {
);
};

const keyCode = key => (key === 'Meta' ? 'MetaLeft' : 'Key' + key.toUpperCase());

const isTextInput = ({ tagName, type }) => {
// input types that aren't variations of text input
const nonTextInputs = [
'button',
'checkbox',
'color',
'file',
'image',
'radio',
'range',
'reset',
'submit',
];

switch (tagName.toLowerCase()) {
case 'input':
return !nonTextInputs.includes(type);
case 'textarea':
return true;
default:
return false;
}
};

const modifierKey = key => ['KeyALT', 'KeyCONTROL'].indexOf(keyCode(key)) > -1;

const handleKeyDown = (commit, e, isEditable) => {
const { key } = e;

if (isEditable && !modifierKey(key)) {
commit('keyboardEvent', {
event: 'keyDown',
code: keyCode(key), // convert to standard event code
});
}
};

const handleKeyPress = (commit, e, isEditable) => {
const { key, target } = e;
const upcaseKey = key && key.toUpperCase();
if (isEditable && !isTextInput(target) && 'GU'.indexOf(upcaseKey) !== -1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see isTextInput is disused, is it the case that the new handler already isn't active when the input target is a text input tag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tested typing in an advanced filter and it appears that the handler doesn't correctly handle the event target being an input control. I'll add the check to the key handler.

commit('actionEvent', {
event: upcaseKey === 'G' ? 'group' : 'ungroup',
});
}
};

const handleKeyUp = (commit, { key }, isEditable) => {
if (isEditable && !modifierKey(key)) {
commit('keyboardEvent', {
event: 'keyUp',
code: keyCode(key), // convert to standard event code
});
}
};

export const eventHandlers = {
onMouseDown: props => e => handleMouseDown(props.commit, e, props.isEditable),
onMouseMove: props => e => handleMouseMove(props.commit, e, props.isEditable),
onKeyDown: props => e => handleKeyDown(props.commit, e, props.isEditable),
onKeyPress: props => e => handleKeyPress(props.commit, e, props.isEditable),
onKeyUp: props => e => handleKeyUp(props.commit, e, props.isEditable),
onKeyDown: props => () => props.commit('keyboardEvent'), // dummy event
onWheel: props => e => handleWheel(props.commit, e, props.isEditable),
resetHandler: () => () => resetHandler(),
};
17 changes: 14 additions & 3 deletions x-pack/plugins/canvas/public/components/workpad_page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,10 @@ export const WorkpadPage = compose(
];
};

const selectedPrimaryShapeObjects = selectedPrimaryShapes.map(id =>
shapes.find(s => s.id === id)
);
const selectedPrimaryShapeObjects = selectedPrimaryShapes
.map(id => shapes.find(s => s.id === id))
.filter(shape => shape);

const selectedPersistentPrimaryShapes = flatten(
selectedPrimaryShapeObjects.map(shape =>
shape.subtype === 'adHocGroup'
Expand Down Expand Up @@ -217,6 +218,16 @@ export const WorkpadPage = compose(
};
}
), // Updates states; needs to have both local and global
withHandlers({
groupElements: ({ commit }) => () =>
commit('actionEvent', {
event: 'group',
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

}),
ungroupElements: ({ commit }) => () =>
commit('actionEvent', {
event: 'ungroup',
}),
}),
withHandlers(eventHandlers) // Captures user intent, needs to have reconciled state
)(Component);

Expand Down
Loading