[Canvas] Feat: Keyboard shortcuts for nudging elements (#39208)

* Added nudge shortcuts

* Cleaned up props

* Updated storyshot

* Changed offset back to 10

* Updated keyboard shortcuts storyshot
This commit is contained in:
Catherine Liu 2019-06-25 09:28:54 -07:00 committed by GitHub
parent 880ae53181
commit fd32269c9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 368 additions and 7 deletions

View file

@ -25,3 +25,5 @@ export const DEFAULT_WORKPAD_CSS = '.canvasPage {\n\n}';
export const DEFAULT_ELEMENT_CSS = '.canvasRenderEl{\n\n}';
export const VALID_IMAGE_TYPES = ['gif', 'jpeg', 'png', 'svg+xml'];
export const ASSET_MAX_SIZE = 25000;
export const ELEMENT_SHIFT_OFFSET = 10;
export const ELEMENT_NUDGE_OFFSET = 1;

View file

@ -451,6 +451,218 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift up by 10px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift down by 10px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift left by 10px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift right by 10px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift up by 1px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
SHIFT
</code>
</span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift down by 1px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
SHIFT
</code>
</span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift left by 1px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
SHIFT
</code>
</span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
<dt
className="euiDescriptionList__title"
>
Shift right by 1px
</dt>
<dd
className="euiDescriptionList__description"
>
<span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
SHIFT
</code>
</span>
<span
className="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
style={Object {}}
>
<code
className="euiCodeBlock__code"
>
</code>
</span>
</span>
</dd>
</dl>
<div
className="euiSpacer euiSpacer--l"

View file

@ -49,4 +49,5 @@ export const interactiveWorkpadPagePropTypes = {
canvasOrigin: PropTypes.func,
saveCanvasOrigin: PropTypes.func.isRequired,
commit: PropTypes.func.isRequired,
setMultiplePositions: PropTypes.func.isRequired,
};

View file

@ -11,7 +11,12 @@ import { updater } from '../../../lib/aeroelastic/layout';
import { getNodes, getPageById, isWriteable } from '../../../state/selectors/workpad';
import { flatten } from '../../../lib/aeroelastic/functional';
import { canUserWrite, getFullscreen } from '../../../state/selectors/app';
import { elementLayer, insertNodes, removeElements } from '../../../state/actions/elements';
import {
elementLayer,
insertNodes,
removeElements,
setMultiplePositions,
} from '../../../state/actions/elements';
import { selectToplevelNodes } from '../../../state/actions/transient';
import { crawlTree, globalStateUpdater, shapesForNodes } from '../integration_utils';
import { InteractiveWorkpadPage as InteractiveComponent } from './interactive_workpad_page';
@ -117,10 +122,12 @@ const mapDispatchToProps = dispatch => ({
removeNodes: (nodeIds, pageId) => dispatch(removeElements(nodeIds, pageId)),
selectToplevelNodes: nodes =>
dispatch(selectToplevelNodes(nodes.filter(e => !e.position.parent).map(e => e.id))),
// TODO: Abstract this out, this is similar to layering code in sidebar/index.js:
elementLayer: (pageId, elementId, movement) => {
dispatch(elementLayer({ pageId, elementId, movement }));
},
elementLayer: (pageId, elementId, movement) =>
dispatch(elementLayer({ pageId, elementId, movement })),
setMultiplePositions: pageId => repositionedNodes =>
dispatch(
setMultiplePositions(repositionedNodes.map(node => ({ ...node, pageId, elementId: node.id })))
),
});
const mergeProps = (
@ -132,6 +139,7 @@ const mergeProps = (
...restDispatchProps,
...restStateProps,
updateGlobalState: globalStateUpdater(dispatch, state),
setMultiplePositions: restDispatchProps.setMultiplePositions(ownProps.pageId),
});
export const InteractivePage = compose(

View file

@ -47,6 +47,7 @@ export class InteractiveWorkpadPage extends PureComponent {
canvasOrigin,
saveCanvasOrigin,
commit,
setMultiplePositions,
} = this.props;
let shortcuts = null;
@ -59,6 +60,7 @@ export class InteractiveWorkpadPage extends PureComponent {
selectedNodes,
selectToplevelNodes,
commit,
setMultiplePositions,
};
shortcuts = <WorkpadShortcuts {...shortcutProps} />;

View file

@ -13,13 +13,15 @@ import {
basicHandlerCreators,
clipboardHandlerCreators,
Props as HandlerCreatorProps,
positionHandlerCreators,
} from '../../lib/element_handler_creators';
export const WorkpadShortcuts = compose<WorkpadShortcutsProps, HandlerCreatorProps>(
withHandlers(groupHandlerCreators),
withHandlers(layerHandlerCreators),
withHandlers(basicHandlerCreators),
withHandlers(clipboardHandlerCreators)
withHandlers(clipboardHandlerCreators),
withHandlers(positionHandlerCreators)
)(Component);
WorkpadShortcuts.propTypes = {

View file

@ -56,6 +56,38 @@ export interface Props {
* ungroups selected group
*/
ungroupNodes: () => void;
/**
* shifts selected elements 10px up
*/
shiftUp: () => void;
/**
* shifts selected elements 10px down
*/
shiftDown: () => void;
/**
* shifts selected elements 10px left
*/
shiftLeft: () => void;
/**
* shifts selected elements 10px right
*/
shiftRight: () => void;
/**
* nudges selected elements 1px up
*/
nudgeUp: () => void;
/**
* nudges selected elements 1px down
*/
nudgeDown: () => void;
/**
* nudges selected elements 1px left
*/
nudgeLeft: () => void;
/**
* nudges selected elements 1px right
*/
nudgeRight: () => void;
}
export class WorkpadShortcuts extends Component<Props> {
@ -71,6 +103,14 @@ export class WorkpadShortcuts extends Component<Props> {
SEND_TO_BACK: this.props.sendToBack,
GROUP: this.props.groupNodes,
UNGROUP: this.props.ungroupNodes,
SHIFT_UP: this.props.shiftUp,
SHIFT_DOWN: this.props.shiftDown,
SHIFT_LEFT: this.props.shiftLeft,
SHIFT_RIGHT: this.props.shiftRight,
NUDGE_UP: this.props.nudgeUp,
NUDGE_DOWN: this.props.nudgeDown,
NUDGE_LEFT: this.props.nudgeLeft,
NUDGE_RIGHT: this.props.nudgeRight,
};
public render() {

View file

@ -15,6 +15,7 @@ import { notify } from './notify';
import * as customElementService from './custom_element_service';
import { getId } from './get_id';
import { PositionedElement } from './positioned_element';
import { ELEMENT_NUDGE_OFFSET, ELEMENT_SHIFT_OFFSET } from '../../common/lib/constants';
const extractId = (node: { id: string }): string => node.id;
@ -47,9 +48,13 @@ export interface Props {
* commits events to layout engine
*/
commit: (eventType: string, config: { event: string }) => void;
/**
* sets new position for multiple elements
*/
setMultiplePositions: (elements: PositionedElement[]) => void;
}
// handlers for clone and delete
// handlers for clone, delete, and saving custom elements
export const basicHandlerCreators = {
cloneNodes: ({ insertNodes, pageId, selectToplevelNodes, selectedNodes }: Props) => (): void => {
const clonedNodes = selectedNodes && cloneSubgraphs(selectedNodes);
@ -154,3 +159,71 @@ export const layerHandlerCreators = {
}
},
};
// handlers for shifting elements up, down, left, and right
export const positionHandlerCreators = {
shiftUp: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.top -= ELEMENT_SHIFT_OFFSET;
return element;
})
);
},
shiftDown: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.top += ELEMENT_SHIFT_OFFSET;
return element;
})
);
},
shiftLeft: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.left -= ELEMENT_SHIFT_OFFSET;
return element;
})
);
},
shiftRight: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.left += ELEMENT_SHIFT_OFFSET;
return element;
})
);
},
nudgeUp: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.top -= ELEMENT_NUDGE_OFFSET;
return element;
})
);
},
nudgeDown: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.top += ELEMENT_NUDGE_OFFSET;
return element;
})
);
},
nudgeLeft: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.left -= ELEMENT_NUDGE_OFFSET;
return element;
})
);
},
nudgeRight: ({ selectedNodes, setMultiplePositions }: Props) => (): void => {
setMultiplePositions(
selectedNodes.map(element => {
element.position.left += ELEMENT_NUDGE_OFFSET;
return element;
})
);
},
};

View file

@ -5,6 +5,7 @@
*/
import { mapValues } from 'lodash';
import { ELEMENT_NUDGE_OFFSET, ELEMENT_SHIFT_OFFSET } from '../../common/lib/constants';
export interface ShortcutMap {
osx: string[];
@ -86,6 +87,26 @@ export const keymap: KeyMap = {
SEND_TO_BACK: getShortcuts('down', { modifiers: ['ctrl', 'shift'], help: 'Send to back' }),
GROUP: getShortcuts('g', { help: 'Group' }),
UNGROUP: getShortcuts('u', { help: 'Ungroup' }),
SHIFT_UP: getShortcuts('up', { help: `Shift up by ${ELEMENT_SHIFT_OFFSET}px` }),
SHIFT_DOWN: getShortcuts('down', { help: `Shift down by ${ELEMENT_SHIFT_OFFSET}px` }),
SHIFT_LEFT: getShortcuts('left', { help: `Shift left by ${ELEMENT_SHIFT_OFFSET}px` }),
SHIFT_RIGHT: getShortcuts('right', { help: `Shift right by ${ELEMENT_SHIFT_OFFSET}px` }),
NUDGE_UP: getShortcuts('up', {
modifiers: ['shift'],
help: `Shift up by ${ELEMENT_NUDGE_OFFSET}px`,
}),
NUDGE_DOWN: getShortcuts('down', {
modifiers: ['shift'],
help: `Shift down by ${ELEMENT_NUDGE_OFFSET}px`,
}),
NUDGE_LEFT: getShortcuts('left', {
modifiers: ['shift'],
help: `Shift left by ${ELEMENT_NUDGE_OFFSET}px`,
}),
NUDGE_RIGHT: getShortcuts('right', {
modifiers: ['shift'],
help: `Shift right by ${ELEMENT_NUDGE_OFFSET}px`,
}),
},
EXPRESSION: {
displayName: 'Expression controls',