-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Changes from all commits
87bb9f7
6c84053
ba58542
36aa689
16813e0
46bfc5e
870d4f2
a6a897d
7b2fa27
daaa871
f6b0c13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> | ||
); | ||
} | ||
} |
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 |
---|---|---|
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
|
@@ -217,6 +218,16 @@ export const WorkpadPage = compose( | |
}; | ||
} | ||
), // Updates states; needs to have both local and global | ||
withHandlers({ | ||
groupElements: ({ commit }) => () => | ||
commit('actionEvent', { | ||
event: 'group', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!