mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Updated redo shortcuts * Moved element deleting handling from event_handler.js to keyHandler used in the Shortcut component Added shortcut for duplicating elements Removed cmd/ctrl+y for redo. conflicts with google chrome Added backspace to navigate back a slide in presentation mode fixed presentation shortcuts Added comments Fixed duplicate elements function Refactored event handlers Added shortcuts for layer manipulation * Added TODOs * Added TODO * Reverted TS changes in keymap.js * Fixed relayer handlers * Fixed remove element * Disables layer manipulation shortcuts when multiple elements are selected * Added comment
This commit is contained in:
parent
56075e0190
commit
af6299bb66
4 changed files with 146 additions and 58 deletions
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { withHandlers } from 'recompose';
|
||||
|
||||
const ancestorElement = element => {
|
||||
if (!element) {
|
||||
return element;
|
||||
|
@ -137,19 +135,14 @@ const isTextInput = ({ tagName, type }) => {
|
|||
|
||||
const modifierKey = key => ['KeyALT', 'KeyCONTROL'].indexOf(keyCode(key)) > -1;
|
||||
|
||||
const handleKeyDown = (commit, e, isEditable, remove) => {
|
||||
const { key, target } = e;
|
||||
const handleKeyDown = (commit, e, isEditable) => {
|
||||
const { key } = e;
|
||||
|
||||
if (isEditable) {
|
||||
if ((key === 'Backspace' || key === 'Delete') && !isTextInput(target)) {
|
||||
e.preventDefault();
|
||||
remove();
|
||||
} else if (!modifierKey(key)) {
|
||||
commit('keyboardEvent', {
|
||||
event: 'keyDown',
|
||||
code: keyCode(key), // convert to standard event code
|
||||
});
|
||||
}
|
||||
if (isEditable && !modifierKey(key)) {
|
||||
commit('keyboardEvent', {
|
||||
event: 'keyDown',
|
||||
code: keyCode(key), // convert to standard event code
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -172,12 +165,12 @@ const handleKeyUp = (commit, { key }, isEditable) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const withEventHandlers = withHandlers({
|
||||
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, props.remove),
|
||||
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),
|
||||
onWheel: props => e => handleWheel(props.commit, e, props.isEditable),
|
||||
resetHandler: () => () => resetHandler(),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, withState, withProps } from 'recompose';
|
||||
import { compose, withState, withProps, withHandlers } from 'recompose';
|
||||
import { notify } from '../../lib/notify';
|
||||
import { aeroelastic } from '../../lib/aeroelastic_kibana';
|
||||
import { setClipboardData, getClipboardData } from '../../lib/clipboard';
|
||||
import { cloneSubgraphs } from '../../lib/clone_subgraphs';
|
||||
import { removeElements, insertNodes } from '../../state/actions/elements';
|
||||
import { removeElements, insertNodes, elementLayer } from '../../state/actions/elements';
|
||||
import { getFullscreen, canUserWrite } from '../../state/selectors/app';
|
||||
import { getNodes, isWriteable } from '../../state/selectors/workpad';
|
||||
import { flatten } from '../../lib/aeroelastic/functional';
|
||||
import { withEventHandlers } from './event_handlers';
|
||||
import { eventHandlers } from './event_handlers';
|
||||
import { WorkpadPage as Component } from './workpad_page';
|
||||
import { selectElement } from './../../state/actions/transient';
|
||||
|
||||
|
@ -31,6 +31,16 @@ const mapDispatchToProps = dispatch => {
|
|||
insertNodes: pageId => selectedElements => dispatch(insertNodes(selectedElements, pageId)),
|
||||
removeElements: pageId => elementIds => dispatch(removeElements(elementIds, pageId)),
|
||||
selectElement: selectedElement => dispatch(selectElement(selectedElement)),
|
||||
// TODO: Abstract this out. This is the same code as in sidebar/index.js
|
||||
elementLayer: (pageId, selectedElement, movement) => {
|
||||
dispatch(
|
||||
elementLayer({
|
||||
pageId,
|
||||
elementId: selectedElement.id,
|
||||
movement,
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -84,6 +94,7 @@ export const WorkpadPage = compose(
|
|||
insertNodes,
|
||||
removeElements,
|
||||
selectElement,
|
||||
elementLayer,
|
||||
}) => {
|
||||
const { shapes, selectedPrimaryShapes = [], cursor } = aeroelastic.getStore(
|
||||
page.id
|
||||
|
@ -100,6 +111,7 @@ export const WorkpadPage = compose(
|
|||
),
|
||||
];
|
||||
};
|
||||
|
||||
const selectedPrimaryShapeObjects = selectedPrimaryShapes.map(id =>
|
||||
shapes.find(s => s.id === id)
|
||||
);
|
||||
|
@ -131,7 +143,7 @@ export const WorkpadPage = compose(
|
|||
// TODO: remove this, it's a hack to force react to rerender
|
||||
setUpdateCount(updateCount + 1);
|
||||
},
|
||||
remove: () => {
|
||||
removeElements: () => {
|
||||
// currently, handle the removal of one element, exploiting multiselect subsequently
|
||||
if (selectedElementIds.length) {
|
||||
removeElements(page.id)(selectedElementIds);
|
||||
|
@ -150,6 +162,27 @@ export const WorkpadPage = compose(
|
|||
notify.success('Copied element to clipboard');
|
||||
}
|
||||
},
|
||||
// TODO: This is slightly different from the duplicateElements function in sidebar/index.js. Should they be doing the same thing?
|
||||
// This should also be abstracted.
|
||||
duplicateElements: () => {
|
||||
const clonedElements = selectedElements && cloneSubgraphs(selectedElements);
|
||||
if (clonedElements) {
|
||||
insertNodes(page.id)(clonedElements);
|
||||
if (selectedPrimaryShapes.length) {
|
||||
if (selectedElements.length > 1) {
|
||||
// adHocGroup branch (currently, pasting will leave only the 1st element selected, rather than forming a
|
||||
// new adHocGroup - todo)
|
||||
selectElement(clonedElements[0].id);
|
||||
} else {
|
||||
// single element or single persistentGroup branch
|
||||
selectElement(
|
||||
clonedElements[selectedElements.findIndex(s => s.id === selectedPrimaryShapes[0])]
|
||||
.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pasteElements: () => {
|
||||
const { selectedElements, rootShapes } = JSON.parse(getClipboardData()) || {};
|
||||
const clonedElements = selectedElements && cloneSubgraphs(selectedElements);
|
||||
|
@ -171,10 +204,20 @@ export const WorkpadPage = compose(
|
|||
}
|
||||
}
|
||||
},
|
||||
// TODO: Same as above. Abstract these out. This is the same code as in sidebar/index.js
|
||||
// Note: these layer actions only work when a single element is selected
|
||||
bringForward: () =>
|
||||
selectedElements.length === 1 && elementLayer(page.id, selectedElements[0], 1),
|
||||
bringToFront: () =>
|
||||
selectedElements.length === 1 && elementLayer(page.id, selectedElements[0], Infinity),
|
||||
sendBackward: () =>
|
||||
selectedElements.length === 1 && elementLayer(page.id, selectedElements[0], -1),
|
||||
sendToBack: () =>
|
||||
selectedElements.length === 1 && elementLayer(page.id, selectedElements[0], -Infinity),
|
||||
};
|
||||
}
|
||||
), // Updates states; needs to have both local and global
|
||||
withEventHandlers // Captures user intent, needs to have reconciled state
|
||||
withHandlers(eventHandlers) // Captures user intent, needs to have reconciled state
|
||||
)(Component);
|
||||
|
||||
WorkpadPage.propTypes = {
|
||||
|
|
|
@ -47,7 +47,13 @@ export class WorkpadPage extends PureComponent {
|
|||
resetHandler: PropTypes.func,
|
||||
copyElements: PropTypes.func,
|
||||
cutElements: PropTypes.func,
|
||||
duplicateElements: PropTypes.func,
|
||||
pasteElements: PropTypes.func,
|
||||
removeElements: PropTypes.func,
|
||||
bringForward: PropTypes.func,
|
||||
bringToFront: PropTypes.func,
|
||||
sendBackward: PropTypes.func,
|
||||
sendToBack: PropTypes.func,
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -73,22 +79,47 @@ export class WorkpadPage extends PureComponent {
|
|||
onMouseUp,
|
||||
onAnimationEnd,
|
||||
onWheel,
|
||||
removeElements,
|
||||
copyElements,
|
||||
cutElements,
|
||||
duplicateElements,
|
||||
pasteElements,
|
||||
bringForward,
|
||||
bringToFront,
|
||||
sendBackward,
|
||||
sendToBack,
|
||||
} = this.props;
|
||||
|
||||
const keyHandler = action => {
|
||||
const keyHandler = (action, event) => {
|
||||
event.preventDefault();
|
||||
switch (action) {
|
||||
case 'COPY':
|
||||
copyElements();
|
||||
break;
|
||||
case 'CLONE':
|
||||
duplicateElements();
|
||||
break;
|
||||
case 'CUT':
|
||||
cutElements();
|
||||
break;
|
||||
case 'DELETE':
|
||||
removeElements();
|
||||
break;
|
||||
case 'PASTE':
|
||||
pasteElements();
|
||||
break;
|
||||
case 'BRING_FORWARD':
|
||||
bringForward();
|
||||
break;
|
||||
case 'BRING_TO_FRONT':
|
||||
bringToFront();
|
||||
break;
|
||||
case 'SEND_BACKWARD':
|
||||
sendBackward();
|
||||
break;
|
||||
case 'SEND_TO_BACK':
|
||||
sendToBack();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,49 +4,70 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
const refresh = { osx: 'option+r', windows: 'alt+r', linux: 'alt+r', other: 'alt+r' };
|
||||
import { mapValues } from 'lodash';
|
||||
|
||||
// maps 'option' for mac and 'alt' for other OS
|
||||
const getAltShortcuts = shortcuts => {
|
||||
if (!Array.isArray(shortcuts)) {
|
||||
shortcuts = [shortcuts];
|
||||
}
|
||||
const optionShortcuts = shortcuts.map(shortcut => `option+${shortcut}`);
|
||||
const altShortcuts = shortcuts.map(shortcut => `alt+${shortcut}`);
|
||||
|
||||
return {
|
||||
osx: optionShortcuts,
|
||||
windows: altShortcuts,
|
||||
linux: altShortcuts,
|
||||
other: altShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
// maps 'command' for mac and 'ctrl' for other OS
|
||||
const getCtrlShortcuts = shortcuts => {
|
||||
if (!Array.isArray(shortcuts)) {
|
||||
shortcuts = [shortcuts];
|
||||
}
|
||||
const cmdShortcuts = shortcuts.map(shortcut => `command+${shortcut}`);
|
||||
const ctrlShortcuts = shortcuts.map(shortcut => `ctrl+${shortcut}`);
|
||||
|
||||
return {
|
||||
osx: cmdShortcuts,
|
||||
windows: ctrlShortcuts,
|
||||
linux: ctrlShortcuts,
|
||||
other: ctrlShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
const refreshShortcut = getAltShortcuts('r');
|
||||
const previousPageShortcut = getAltShortcuts('[');
|
||||
const nextPageShortcut = getAltShortcuts(']');
|
||||
|
||||
export const keymap = {
|
||||
EDITOR: {
|
||||
UNDO: { osx: 'command+z', windows: 'ctrl+z', linux: 'ctrl+z', other: 'ctrl+z' },
|
||||
REDO: {
|
||||
osx: 'command+shift+y',
|
||||
windows: 'ctrl+shift+y',
|
||||
linux: 'ctrl+shift+y',
|
||||
other: 'ctrl+shift+y',
|
||||
},
|
||||
NEXT: { osx: 'option+]', windows: 'alt+]', linux: 'alt+]', other: 'alt+]' },
|
||||
PREV: { osx: 'option+[', windows: 'alt+[', linux: 'alt+[', other: 'alt+[' },
|
||||
FULLSCREEN: {
|
||||
osx: ['option+p', 'option+f'],
|
||||
windows: ['alt+p', 'alt+f'],
|
||||
linux: ['alt+p', 'alt+f'],
|
||||
other: ['alt+p', 'alt+f'],
|
||||
},
|
||||
UNDO: getCtrlShortcuts('z'),
|
||||
REDO: getCtrlShortcuts('shift+z'),
|
||||
PREV: previousPageShortcut,
|
||||
NEXT: nextPageShortcut,
|
||||
FULLSCREEN: getAltShortcuts(['p', 'f']),
|
||||
FULLSCREEN_EXIT: ['escape'],
|
||||
EDITING: { osx: 'option+e', windows: 'alt+e', linux: 'alt+e', other: 'alt+e' },
|
||||
GRID: { osx: 'option+g', windows: 'alt+g', linux: 'alt+g', other: 'alt+g' },
|
||||
REFRESH: refresh,
|
||||
EDITING: getAltShortcuts('e'),
|
||||
GRID: getAltShortcuts('g'),
|
||||
REFRESH: refreshShortcut,
|
||||
},
|
||||
ELEMENT: {
|
||||
COPY: { osx: 'command+c', windows: 'ctrl+c', linux: 'ctrl+c', other: 'ctrl+c' },
|
||||
CUT: { osx: 'command+x', windows: 'ctrl+x', linux: 'ctrl+x', other: 'ctrl+x' },
|
||||
PASTE: { osx: 'command+v', windows: 'ctrl+v', linux: 'ctrl+v', other: 'ctrl+v' },
|
||||
COPY: getCtrlShortcuts('c'),
|
||||
CLONE: getCtrlShortcuts('d'),
|
||||
CUT: getCtrlShortcuts('x'),
|
||||
PASTE: getCtrlShortcuts('v'),
|
||||
DELETE: ['del', 'backspace'],
|
||||
BRING_FORWARD: getCtrlShortcuts('up'),
|
||||
SEND_BACKWARD: getCtrlShortcuts('down'),
|
||||
BRING_TO_FRONT: getCtrlShortcuts('shift+up'),
|
||||
SEND_TO_BACK: getCtrlShortcuts('shift+down'),
|
||||
},
|
||||
PRESENTATION: {
|
||||
NEXT: {
|
||||
osx: ['space', 'right', 'option+]'],
|
||||
windows: ['space', 'right', 'alt+]'],
|
||||
linux: ['space', 'right', 'alt+]'],
|
||||
other: ['space', 'right', 'alt+]'],
|
||||
},
|
||||
PREV: {
|
||||
osx: ['left', 'option+['],
|
||||
windows: ['left', 'alt+['],
|
||||
linux: ['left', 'alt+['],
|
||||
other: ['left', 'alt+['],
|
||||
},
|
||||
REFRESH: refresh,
|
||||
PREV: mapValues(previousPageShortcut, osShortcuts => osShortcuts.concat(['backspace', 'left'])),
|
||||
NEXT: mapValues(nextPageShortcut, osShortcuts => osShortcuts.concat(['space', 'right'])),
|
||||
REFRESH: refreshShortcut,
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue