[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:
parent
880ae53181
commit
fd32269c9a
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -49,4 +49,5 @@ export const interactiveWorkpadPagePropTypes = {
|
|||
canvasOrigin: PropTypes.func,
|
||||
saveCanvasOrigin: PropTypes.func.isRequired,
|
||||
commit: PropTypes.func.isRequired,
|
||||
setMultiplePositions: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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} />;
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue