diff --git a/x-pack/legacy/plugins/canvas/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts
index 83d53123ddde5..108b93d528d7c 100644
--- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts
+++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts
@@ -27,3 +27,6 @@ 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;
+export const ZOOM_LEVELS = [0.25, 0.33, 0.5, 0.67, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4];
+export const MIN_ZOOM_LEVEL = ZOOM_LEVELS[0];
+export const MAX_ZOOM_LEVEL = ZOOM_LEVELS[ZOOM_LEVELS.length - 1];
diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js
index 0618a39075920..3311af01dc18b 100644
--- a/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js
+++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/routes.js
@@ -12,6 +12,7 @@ import { setWorkpad } from '../../state/actions/workpad';
import { setAssets, resetAssets } from '../../state/actions/assets';
import { setPage } from '../../state/actions/pages';
import { getWorkpad } from '../../state/selectors/workpad';
+import { setZoomScale } from '../../state/actions/transient';
import { WorkpadApp } from './workpad_app';
export const routes = [
@@ -51,6 +52,9 @@ export const routes = [
const { assets, ...workpad } = fetchedWorkpad;
dispatch(setWorkpad(workpad));
dispatch(setAssets(assets));
+
+ // reset transient properties when changing workpads
+ dispatch(setZoomScale(1));
} catch (err) {
notify.error(err, { title: `Couldn't load workpad with ID` });
return router.redirectTo('home');
diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss
index a27b18c5b4c57..adbb3a3640d76 100644
--- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss
+++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss
@@ -30,8 +30,10 @@ $canvasLayoutFontSize: $euiFontSizeS;
.canvasLayout__stageHeader {
flex-grow: 0;
flex-basis: auto;
- padding: $euiSizeM $euiSize $euiSizeS $euiSize;
+ padding: ($euiSizeXS +1px) $euiSize $euiSizeXS $euiSize;
font-size: $canvasLayoutFontSize;
+ border-bottom: $euiBorderThin;
+ background: $euiColorLightestShade;
}
.canvasLayout__stageContent {
@@ -60,6 +62,7 @@ $canvasLayoutFontSize: $euiFontSizeS;
background: $euiColorLightestShade;
display: flex;
position: relative;
+ border-left: $euiBorderThin;
.euiPanel {
margin-bottom: $euiSizeS;
diff --git a/x-pack/legacy/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js b/x-pack/legacy/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js
index 704f5934345e7..de9d573724836 100644
--- a/x-pack/legacy/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js
+++ b/x-pack/legacy/plugins/canvas/public/components/border_resize_handle/border_resize_handle.js
@@ -8,10 +8,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import { matrixToCSS } from '../../lib/dom';
-export const BorderResizeHandle = ({ transformMatrix }) => (
+export const BorderResizeHandle = ({ transformMatrix, zoomScale }) => (
{
+ // replace +'s with spaces so we can display the plus symbol for the plus key
+ shortcut = shortcut.replace(/\+/g, ' ');
if (i !== 0) {
acc.push(
or );
}
acc.push(
{getPrettyShortcut(shortcut)
- .split(/(\+)/g) // splits the array by '+' and keeps the '+'s as elements in the array
- .map(key => (key === '+' ? ` ` : {key}))}
+ .split(/( )/g)
+ .map(key => (key === ' ' ? key : {key}))}
);
return acc;
diff --git a/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.js b/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.js
index 335f2e719857f..dfadbbc39c547 100644
--- a/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.js
+++ b/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.js
@@ -8,12 +8,17 @@ import React from 'react';
import PropTypes from 'prop-types';
import { matrixToCSS } from '../../lib/dom';
-export const RotationHandle = ({ transformMatrix }) => (
+export const RotationHandle = ({ transformMatrix, zoomScale }) => (
);
diff --git a/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.scss b/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.scss
index 820107ff47a51..233a86199c483 100644
--- a/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.scss
+++ b/x-pack/legacy/plugins/canvas/public/components/rotation_handle/rotation_handle.scss
@@ -19,7 +19,7 @@
height: 9px;
width: 9px;
margin-left: -5px;
- margin-top: -3px;
+ margin-top: -6px;
border-radius: 50%;
background-color: $euiColorMediumShade;
}
diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss
index 613e101759e74..fe52e652c08a4 100644
--- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss
+++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss
@@ -1,7 +1,3 @@
.canvasLayout__sidebarHeader {
- padding: $euiSizeS 0;
-}
-
-.canvasContextMenu--topBorder {
- border-top: $euiBorderThin;
+ padding: ($euiSizeXS * 0.5) 0;
}
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad/index.js
index c030cb1cabcbc..0ddc24f448681 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad/index.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad/index.js
@@ -10,13 +10,15 @@ import { pure, compose, withState, withProps, getContext, withHandlers } from 'r
import { transitionsRegistry } from '../../lib/transitions_registry';
import { undoHistory, redoHistory } from '../../state/actions/history';
import { fetchAllRenderables } from '../../state/actions/elements';
-import { getFullscreen } from '../../state/selectors/app';
+import { setZoomScale } from '../../state/actions/transient';
+import { getFullscreen, getZoomScale } from '../../state/selectors/app';
import {
getSelectedPageIndex,
getAllElements,
getWorkpad,
getPages,
} from '../../state/selectors/workpad';
+import { zoomHandlerCreators } from '../../lib/app_handler_creators';
import { Workpad as Component } from './workpad';
const mapStateToProps = state => {
@@ -30,6 +32,7 @@ const mapStateToProps = state => {
workpadCss,
workpadId,
isFullscreen: getFullscreen(state),
+ zoomScale: getZoomScale(state),
};
};
@@ -37,6 +40,7 @@ const mapDispatchToProps = {
undoHistory,
redoHistory,
fetchAllRenderables,
+ setZoomScale,
};
export const Workpad = compose(
@@ -92,5 +96,6 @@ export const Workpad = compose(
const pageNumber = Math.max(1, props.selectedPageNumber - 1);
props.onPageChange(pageNumber);
},
- })
+ }),
+ withHandlers(zoomHandlerCreators)
)(Component);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad/workpad.js b/x-pack/legacy/plugins/canvas/public/components/workpad/workpad.js
index 44f7cbea8716d..247f04fd6f899 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad/workpad.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad/workpad.js
@@ -10,6 +10,7 @@ import { Shortcuts } from 'react-shortcuts';
import Style from 'style-it';
import { WorkpadPage } from '../workpad_page';
import { Fullscreen } from '../fullscreen';
+import { isTextInput } from '../../lib/is_text_input';
const WORKPAD_CANVAS_BUFFER = 32; // 32px padding around the workpad
@@ -35,40 +36,25 @@ export class Workpad extends React.PureComponent {
unregisterLayout: PropTypes.func.isRequired,
};
- keyHandler = action => {
- const {
- fetchAllRenderables,
- undoHistory,
- redoHistory,
- nextPage,
- previousPage,
- grid, // TODO: Get rid of grid when we improve the layout engine
- setGrid,
- } = this.props;
-
- // handle keypress events for editor and presentation events
+ // handle keypress events for editor and presentation events
+ _keyMap = {
// this exists in both contexts
- if (action === 'REFRESH') {
- return fetchAllRenderables();
- }
-
+ REFRESH: this.props.fetchAllRenderables,
// editor events
- if (action === 'UNDO') {
- return undoHistory();
- }
- if (action === 'REDO') {
- return redoHistory();
- }
- if (action === 'GRID') {
- return setGrid(!grid);
- }
-
+ UNDO: this.props.undoHistory,
+ REDO: this.props.redoHistory,
+ GRID: () => this.props.setGrid(!this.props.grid),
+ ZOOM_IN: this.props.zoomIn,
+ ZOOM_OUT: this.props.zoomOut,
// presentation events
- if (action === 'PREV') {
- return previousPage();
- }
- if (action === 'NEXT') {
- return nextPage();
+ PREV: this.props.previousPage,
+ NEXT: this.props.nextPage,
+ };
+
+ _keyHandler = (action, event) => {
+ if (!isTextInput(event.target)) {
+ event.preventDefault();
+ this._keyMap[action]();
}
};
@@ -86,18 +72,27 @@ export class Workpad extends React.PureComponent {
isFullscreen,
registerLayout,
unregisterLayout,
+ zoomScale,
} = this.props;
const bufferStyle = {
- height: isFullscreen ? height : height + WORKPAD_CANVAS_BUFFER,
- width: isFullscreen ? width : width + WORKPAD_CANVAS_BUFFER,
+ height: isFullscreen ? height : (height + 2 * WORKPAD_CANVAS_BUFFER) * zoomScale,
+ width: isFullscreen ? width : (width + 2 * WORKPAD_CANVAS_BUFFER) * zoomScale,
};
return (
-
+
{!isFullscreen && (
-
+
)}
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_header/index.js
index b3de256155d56..d4ecddebde1ae 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/index.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/index.js
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { compose } from 'recompose';
import { connect } from 'react-redux';
import { canUserWrite } from '../../state/selectors/app';
import { getSelectedPage, isWriteable } from '../../state/selectors/workpad';
@@ -25,13 +24,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...stateProps,
...dispatchProps,
...ownProps,
- toggleWriteable: () => dispatchProps.setWriteable(!stateProps.isWriteable),
+ toggleWriteable: () => setWriteable(!stateProps.isWriteable),
});
-export const WorkpadHeader = compose(
- connect(
- mapStateToProps,
- mapDispatchToProps,
- mergeProps
- )
+export const WorkpadHeader = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+ mergeProps
)(Component);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.js b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.js
index 3988e614347d3..5cd11058934c5 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.js
@@ -24,6 +24,7 @@ import { ControlSettings } from './control_settings';
import { RefreshControl } from './refresh_control';
import { FullscreenControl } from './fullscreen_control';
import { WorkpadExport } from './workpad_export';
+import { WorkpadZoom } from './workpad_zoom';
export class WorkpadHeader extends React.PureComponent {
static propTypes = {
@@ -131,6 +132,9 @@ export class WorkpadHeader extends React.PureComponent {
/>
+
+
+
{isWriteable ? (
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx
new file mode 100644
index 0000000000000..55ceb3f65004f
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 { compose, withHandlers } from 'recompose';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+// @ts-ignore unconverted local file
+import { getZoomScale } from '../../../state/selectors/app';
+// @ts-ignore unconverted local file
+import { setZoomScale } from '../../../state/actions/transient';
+import { zoomHandlerCreators } from '../../../lib/app_handler_creators';
+import { WorkpadZoom as Component, Props as ComponentProps } from './workpad_zoom';
+
+interface State {
+ transient: { zoomScale: number };
+}
+
+const mapStateToProps = (state: State) => ({
+ zoomScale: getZoomScale(state),
+});
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+ setZoomScale: (scale: number) => dispatch(setZoomScale(scale)),
+});
+
+export const WorkpadZoom = compose(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ ),
+ withHandlers(zoomHandlerCreators)
+)(Component);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss
new file mode 100644
index 0000000000000..44209aaa72d63
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss
@@ -0,0 +1,11 @@
+.canvasWorkpadExport__panelContent {
+ padding: $euiSize;
+}
+
+.canvasWorkpadExport__reportingConfig {
+ .euiCodeBlock__pre {
+ @include euiScrollBar;
+ overflow-x: auto;
+ white-space: pre;
+ }
+}
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx
new file mode 100644
index 0000000000000..c4ba3a32f51b7
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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, { MouseEventHandler, PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import {
+ EuiButtonIcon,
+ EuiContextMenu,
+ EuiContextMenuPanelDescriptor,
+ EuiContextMenuPanelItemDescriptor,
+} from '@elastic/eui';
+// @ts-ignore unconverted local component
+import { Popover } from '../../popover';
+import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../../../common/lib/constants';
+
+export interface Props {
+ /**
+ * current workpad zoom level
+ */
+ zoomScale: number;
+ /**
+ * handler to set the workpad zoom level to a specific value
+ */
+ setZoomScale: (scale: number) => void;
+ /**
+ * handler to increase the workpad zoom level
+ */
+ zoomIn: () => void;
+ /**
+ * handler to decrease workpad zoom level
+ */
+ zoomOut: () => void;
+}
+
+const QUICK_ZOOM_LEVELS = [0.5, 1, 2];
+
+export class WorkpadZoom extends PureComponent {
+ static propTypes = {
+ zoomScale: PropTypes.number.isRequired,
+ setZoomScale: PropTypes.func.isRequired,
+ zoomIn: PropTypes.func.isRequired,
+ zoomOut: PropTypes.func.isRequired,
+ };
+
+ _button = (togglePopover: MouseEventHandler) => (
+
+ );
+
+ _getPrettyZoomLevel = (scale: number) => `${scale * 100}%`;
+
+ _getScaleMenuItems = (): EuiContextMenuPanelItemDescriptor[] =>
+ QUICK_ZOOM_LEVELS.map(scale => ({
+ name: this._getPrettyZoomLevel(scale),
+ icon: 'empty',
+ onClick: () => this.props.setZoomScale(scale),
+ }));
+
+ _getPanels = (): EuiContextMenuPanelDescriptor[] => {
+ const { zoomScale, zoomIn, zoomOut } = this.props;
+ const items: EuiContextMenuPanelItemDescriptor[] = [
+ ...this._getScaleMenuItems(),
+ {
+ name: 'Zoom in',
+ icon: 'starPlusFilled', // TODO: change this to magnifyWithPlus when available
+ onClick: zoomIn,
+ disabled: zoomScale === MAX_ZOOM_LEVEL,
+ className: 'canvasContextMenu--topBorder',
+ },
+ {
+ name: 'Zoom out',
+ icon: 'starMinusFilled', // TODO: change this to magnifyWithMinus when available
+ onClick: zoomOut,
+ disabled: zoomScale === MIN_ZOOM_LEVEL,
+ },
+ ];
+
+ const panels: EuiContextMenuPanelDescriptor[] = [
+ {
+ id: 0,
+ title: `Zoom`,
+ items,
+ },
+ ];
+
+ return panels;
+ };
+
+ render() {
+ return (
+
+ {() => }
+
+ );
+ }
+}
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/prop_types.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/prop_types.js
index a156582b6f510..040dfb21662ea 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/prop_types.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/prop_types.js
@@ -50,4 +50,5 @@ export const interactiveWorkpadPagePropTypes = {
saveCanvasOrigin: PropTypes.func.isRequired,
commit: PropTypes.func.isRequired,
setMultiplePositions: PropTypes.func.isRequired,
+ zoomScale: PropTypes.number.isRequired,
};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js
index 05214f59d3931..8f81d3e1c808c 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js
@@ -4,11 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-const localMousePosition = (canvasOrigin, clientX, clientY) => {
+const localMousePosition = (canvasOrigin, clientX, clientY, zoomScale = 1) => {
const { left, top } = canvasOrigin();
return {
- x: clientX - left,
- y: clientY - top,
+ // commit unscaled coordinates
+ x: (clientX - left) / zoomScale,
+ y: (clientY - top) / zoomScale,
};
};
@@ -17,12 +18,12 @@ const resetHandler = () => {
window.onmouseup = null;
};
-const setupHandler = (commit, canvasOrigin) => {
+const setupHandler = (commit, canvasOrigin, zoomScale) => {
// Ancestor has to be identified on setup, rather than 1st interaction, otherwise events may be triggered on
// DOM elements that had been removed: kibana-canvas github issue #1093
window.onmousemove = ({ buttons, clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }) => {
- const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
+ const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale);
// only commits the cursor position if there's a way to latch onto x/y calculation (canvasOrigin is knowable)
// or if left button is being held down (i.e. an element is being dragged)
if (buttons === 1 || canvasOrigin) {
@@ -35,7 +36,7 @@ const setupHandler = (commit, canvasOrigin) => {
window.onmouseup = e => {
e.stopPropagation();
const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey } = e;
- const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
+ const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale);
commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey, ctrlKey });
resetHandler();
};
@@ -44,9 +45,10 @@ const setupHandler = (commit, canvasOrigin) => {
const handleMouseMove = (
commit,
{ clientX, clientY, altKey, metaKey, shiftKey, ctrlKey },
- canvasOrigin
+ canvasOrigin,
+ zoomScale
) => {
- const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
+ const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale);
if (commit) {
commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey });
}
@@ -58,21 +60,21 @@ const handleMouseLeave = (commit, { buttons }) => {
}
};
-const handleMouseDown = (commit, e, canvasOrigin) => {
+const handleMouseDown = (commit, e, canvasOrigin, zoomScale) => {
e.stopPropagation();
const { clientX, clientY, buttons, altKey, metaKey, shiftKey, ctrlKey } = e;
if (buttons !== 1 || !commit) {
resetHandler();
return; // left-click only
}
- setupHandler(commit, canvasOrigin);
- const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
+ setupHandler(commit, canvasOrigin, zoomScale);
+ const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale);
commit('mouseEvent', { event: 'mouseDown', x, y, altKey, metaKey, shiftKey, ctrlKey });
};
export const eventHandlers = {
- onMouseDown: props => e => handleMouseDown(props.commit, e, props.canvasOrigin),
- onMouseMove: props => e => handleMouseMove(props.commit, e, props.canvasOrigin),
+ onMouseDown: props => e => handleMouseDown(props.commit, e, props.canvasOrigin, props.zoomScale),
+ onMouseMove: props => e => handleMouseMove(props.commit, e, props.canvasOrigin, props.zoomScale),
onMouseLeave: props => e => handleMouseLeave(props.commit, e),
onWheel: props => e => handleMouseMove(props.commit, e, props.canvasOrigin),
resetHandler: () => () => resetHandler(),
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js
index 91a6cf489736a..790070c37d6f9 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js
@@ -10,7 +10,7 @@ import { createStore } from '../../../lib/aeroelastic/store';
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 { canUserWrite, getFullscreen, getZoomScale } from '../../../state/selectors/app';
import {
elementLayer,
insertNodes,
@@ -113,6 +113,7 @@ const mapStateToProps = (state, ownProps) => {
selectedToplevelNodes,
selectedNodes: selectedNodeIds.map(id => nodes.find(s => s.id === id)),
pageStyle: getPageById(state, ownProps.pageId).style,
+ zoomScale: getZoomScale(state),
};
};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js
index 8d32d47b4d3f7..7e4a4eb581e60 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interactive_workpad_page.js
@@ -48,6 +48,7 @@ export class InteractiveWorkpadPage extends PureComponent {
saveCanvasOrigin,
commit,
setMultiplePositions,
+ zoomScale,
} = this.props;
let shortcuts = null;
@@ -97,6 +98,7 @@ export class InteractiveWorkpadPage extends PureComponent {
width: node.width,
height: node.height,
text: node.text,
+ zoomScale,
};
switch (node.subtype) {
diff --git a/x-pack/legacy/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts b/x-pack/legacy/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts
index 56384d9a937b8..783b085f3da7e 100644
--- a/x-pack/legacy/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts
+++ b/x-pack/legacy/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts
@@ -9,54 +9,70 @@ import { getPrettyShortcut } from '../get_pretty_shortcut';
describe('getPrettyShortcut', () => {
test('uppercases shortcuts', () => {
expect(getPrettyShortcut('g')).toBe('G');
- expect(getPrettyShortcut('shift+click')).toBe('SHIFT+CLICK');
+ expect(getPrettyShortcut('shift click')).toBe('SHIFT CLICK');
expect(getPrettyShortcut('backspace')).toBe('BACKSPACE');
});
test('preserves shortcut order', () => {
- expect(getPrettyShortcut('command+c')).toBe('⌘+C');
- expect(getPrettyShortcut('c+command')).toBe('C+⌘');
+ expect(getPrettyShortcut('command c')).toBe('⌘ C');
+ expect(getPrettyShortcut('c command')).toBe('C ⌘');
});
test(`replaces 'command' with ⌘`, () => {
expect(getPrettyShortcut('command')).toBe('⌘');
- expect(getPrettyShortcut('command+c')).toBe('⌘+C');
- expect(getPrettyShortcut('command+shift+b')).toBe('⌘+SHIFT+B');
+ expect(getPrettyShortcut('command c')).toBe('⌘ C');
+ expect(getPrettyShortcut('command shift b')).toBe('⌘ SHIFT B');
});
test(`replaces 'option' with ⌥`, () => {
expect(getPrettyShortcut('option')).toBe('⌥');
- expect(getPrettyShortcut('option+f')).toBe('⌥+F');
- expect(getPrettyShortcut('option+shift+G')).toBe('⌥+SHIFT+G');
- expect(getPrettyShortcut('command+option+shift+G')).toBe('⌘+⌥+SHIFT+G');
+ expect(getPrettyShortcut('option f')).toBe('⌥ F');
+ expect(getPrettyShortcut('option shift G')).toBe('⌥ SHIFT G');
+ expect(getPrettyShortcut('command option shift G')).toBe('⌘ ⌥ SHIFT G');
});
test(`replaces 'left' with ←`, () => {
expect(getPrettyShortcut('left')).toBe('←');
- expect(getPrettyShortcut('command+left')).toBe('⌘+←');
- expect(getPrettyShortcut('option+left')).toBe('⌥+←');
- expect(getPrettyShortcut('option+shift+left')).toBe('⌥+SHIFT+←');
- expect(getPrettyShortcut('command+shift+left')).toBe('⌘+SHIFT+←');
- expect(getPrettyShortcut('command+option+shift+left')).toBe('⌘+⌥+SHIFT+←');
+ expect(getPrettyShortcut('command left')).toBe('⌘ ←');
+ expect(getPrettyShortcut('option left')).toBe('⌥ ←');
+ expect(getPrettyShortcut('option shift left')).toBe('⌥ SHIFT ←');
+ expect(getPrettyShortcut('command shift left')).toBe('⌘ SHIFT ←');
+ expect(getPrettyShortcut('command option shift left')).toBe('⌘ ⌥ SHIFT ←');
});
test(`replaces 'right' with →`, () => {
expect(getPrettyShortcut('right')).toBe('→');
- expect(getPrettyShortcut('command+right')).toBe('⌘+→');
- expect(getPrettyShortcut('option+right')).toBe('⌥+→');
- expect(getPrettyShortcut('option+shift+right')).toBe('⌥+SHIFT+→');
- expect(getPrettyShortcut('command+shift+right')).toBe('⌘+SHIFT+→');
- expect(getPrettyShortcut('command+option+shift+right')).toBe('⌘+⌥+SHIFT+→');
+ expect(getPrettyShortcut('command right')).toBe('⌘ →');
+ expect(getPrettyShortcut('option right')).toBe('⌥ →');
+ expect(getPrettyShortcut('option shift right')).toBe('⌥ SHIFT →');
+ expect(getPrettyShortcut('command shift right')).toBe('⌘ SHIFT →');
+ expect(getPrettyShortcut('command option shift right')).toBe('⌘ ⌥ SHIFT →');
});
test(`replaces 'up' with ←`, () => {
expect(getPrettyShortcut('up')).toBe('↑');
- expect(getPrettyShortcut('command+up')).toBe('⌘+↑');
- expect(getPrettyShortcut('option+up')).toBe('⌥+↑');
- expect(getPrettyShortcut('option+shift+up')).toBe('⌥+SHIFT+↑');
- expect(getPrettyShortcut('command+shift+up')).toBe('⌘+SHIFT+↑');
- expect(getPrettyShortcut('command+option+shift+up')).toBe('⌘+⌥+SHIFT+↑');
+ expect(getPrettyShortcut('command up')).toBe('⌘ ↑');
+ expect(getPrettyShortcut('option up')).toBe('⌥ ↑');
+ expect(getPrettyShortcut('option shift up')).toBe('⌥ SHIFT ↑');
+ expect(getPrettyShortcut('command shift up')).toBe('⌘ SHIFT ↑');
+ expect(getPrettyShortcut('command option shift up')).toBe('⌘ ⌥ SHIFT ↑');
});
test(`replaces 'down' with ↓`, () => {
expect(getPrettyShortcut('down')).toBe('↓');
- expect(getPrettyShortcut('command+down')).toBe('⌘+↓');
- expect(getPrettyShortcut('option+down')).toBe('⌥+↓');
- expect(getPrettyShortcut('option+shift+down')).toBe('⌥+SHIFT+↓');
- expect(getPrettyShortcut('command+shift+down')).toBe('⌘+SHIFT+↓');
- expect(getPrettyShortcut('command+option+shift+down')).toBe('⌘+⌥+SHIFT+↓');
+ expect(getPrettyShortcut('command down')).toBe('⌘ ↓');
+ expect(getPrettyShortcut('option down')).toBe('⌥ ↓');
+ expect(getPrettyShortcut('option shift down')).toBe('⌥ SHIFT ↓');
+ expect(getPrettyShortcut('command shift down')).toBe('⌘ SHIFT ↓');
+ expect(getPrettyShortcut('command option shift down')).toBe('⌘ ⌥ SHIFT ↓');
+ });
+ test(`replaces 'plus' with +`, () => {
+ expect(getPrettyShortcut('plus')).toBe('+');
+ expect(getPrettyShortcut('command plus')).toBe('⌘ +');
+ expect(getPrettyShortcut('option plus')).toBe('⌥ +');
+ expect(getPrettyShortcut('option shift plus')).toBe('⌥ SHIFT +');
+ expect(getPrettyShortcut('command shift plus')).toBe('⌘ SHIFT +');
+ expect(getPrettyShortcut('command option shift plus')).toBe('⌘ ⌥ SHIFT +');
+ });
+ test(`replaces 'minus' with -`, () => {
+ expect(getPrettyShortcut('minus')).toBe('-');
+ expect(getPrettyShortcut('command minus')).toBe('⌘ -');
+ expect(getPrettyShortcut('option minus')).toBe('⌥ -');
+ expect(getPrettyShortcut('option shift minus')).toBe('⌥ SHIFT -');
+ expect(getPrettyShortcut('command shift minus')).toBe('⌘ SHIFT -');
+ expect(getPrettyShortcut('command option shift minus')).toBe('⌘ ⌥ SHIFT -');
});
});
diff --git a/x-pack/legacy/plugins/canvas/public/lib/app_handler_creators.ts b/x-pack/legacy/plugins/canvas/public/lib/app_handler_creators.ts
new file mode 100644
index 0000000000000..f0a73046e6d35
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/lib/app_handler_creators.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { ZOOM_LEVELS, MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../common/lib/constants';
+
+export interface Props {
+ /**
+ * current zoom level of the workpad
+ */
+ zoomScale: number;
+ /**
+ * sets the new zoom level
+ */
+ setZoomScale: (scale: number) => void;
+}
+
+// handlers for zooming in and out
+export const zoomHandlerCreators = {
+ zoomIn: ({ zoomScale, setZoomScale }: Props) => (): void => {
+ const scaleIndex = ZOOM_LEVELS.indexOf(zoomScale);
+ const scaleUp =
+ scaleIndex + 1 < ZOOM_LEVELS.length ? ZOOM_LEVELS[scaleIndex + 1] : MAX_ZOOM_LEVEL;
+ setZoomScale(scaleUp);
+ },
+ zoomOut: ({ zoomScale, setZoomScale }: Props) => (): void => {
+ const scaleIndex = ZOOM_LEVELS.indexOf(zoomScale);
+ const scaleDown = scaleIndex - 1 >= 0 ? ZOOM_LEVELS[scaleIndex - 1] : MIN_ZOOM_LEVEL;
+ setZoomScale(scaleDown);
+ },
+};
diff --git a/x-pack/legacy/plugins/canvas/public/lib/get_pretty_shortcut.ts b/x-pack/legacy/plugins/canvas/public/lib/get_pretty_shortcut.ts
index 46165f5c35dc4..30f36f7696d0c 100644
--- a/x-pack/legacy/plugins/canvas/public/lib/get_pretty_shortcut.ts
+++ b/x-pack/legacy/plugins/canvas/public/lib/get_pretty_shortcut.ts
@@ -16,6 +16,8 @@ export const getPrettyShortcut = (shortcut: string): string => {
result = result.replace(/right/i, '→');
result = result.replace(/up/i, '↑');
result = result.replace(/down/i, '↓');
+ result = result.replace(/plus/i, '+');
+ result = result.replace(/minus/i, '-');
return result;
};
diff --git a/x-pack/legacy/plugins/canvas/public/lib/keymap.ts b/x-pack/legacy/plugins/canvas/public/lib/keymap.ts
index d0ee49de37f5e..db0ef2d3b256c 100644
--- a/x-pack/legacy/plugins/canvas/public/lib/keymap.ts
+++ b/x-pack/legacy/plugins/canvas/public/lib/keymap.ts
@@ -40,23 +40,23 @@ const getShortcuts = (
modifiers = [modifiers];
}
- let macShortcuts = shortcuts;
+ let macShortcuts = [...shortcuts];
// handle shift modifier
if (modifiers.includes('shift')) {
- macShortcuts = shortcuts.map(shortcut => `shift+${shortcut}`);
+ macShortcuts = macShortcuts.map(shortcut => `shift+${shortcut}`);
shortcuts = shortcuts.map(shortcut => `shift+${shortcut}`);
}
// handle alt modifier
if (modifiers.includes('alt') || modifiers.includes('option')) {
- macShortcuts = shortcuts.map(shortcut => `option+${shortcut}`);
+ macShortcuts = macShortcuts.map(shortcut => `option+${shortcut}`);
shortcuts = shortcuts.map(shortcut => `alt+${shortcut}`);
}
// handle ctrl modifier
if (modifiers.includes('ctrl') || modifiers.includes('command')) {
- macShortcuts = shortcuts.map(shortcut => `command+${shortcut}`);
+ macShortcuts = macShortcuts.map(shortcut => `command+${shortcut}`);
shortcuts = shortcuts.map(shortcut => `ctrl+${shortcut}`);
}
@@ -138,6 +138,8 @@ export const keymap: KeyMap = {
EDITING: getShortcuts('e', { modifiers: 'alt', help: 'Toggle edit mode' }),
GRID: getShortcuts('g', { modifiers: 'alt', help: 'Show grid' }),
REFRESH: refreshShortcut,
+ ZOOM_IN: getShortcuts('plus', { modifiers: ['ctrl', 'alt'], help: 'Zoom in' }),
+ ZOOM_OUT: getShortcuts('minus', { modifiers: ['ctrl', 'alt'], help: 'Zoom out' }),
},
PRESENTATION: {
displayName: 'Presentation controls',
diff --git a/x-pack/legacy/plugins/canvas/public/state/actions/transient.js b/x-pack/legacy/plugins/canvas/public/state/actions/transient.js
index a87c39b7ef6e0..6f4484638924c 100644
--- a/x-pack/legacy/plugins/canvas/public/state/actions/transient.js
+++ b/x-pack/legacy/plugins/canvas/public/state/actions/transient.js
@@ -10,3 +10,4 @@ export const setFullscreen = createAction('setFullscreen');
export const selectToplevelNodes = createAction('selectToplevelNodes');
export const setFirstLoad = createAction('setFirstLoad');
export const setElementStats = createAction('setElementStats');
+export const setZoomScale = createAction('setZoomScale');
diff --git a/x-pack/legacy/plugins/canvas/public/state/initial_state.js b/x-pack/legacy/plugins/canvas/public/state/initial_state.js
index 2ba4cd1c37f36..4ca4713fa8fb7 100644
--- a/x-pack/legacy/plugins/canvas/public/state/initial_state.js
+++ b/x-pack/legacy/plugins/canvas/public/state/initial_state.js
@@ -14,6 +14,7 @@ export const getInitialState = path => {
assets: {}, // assets end up here
transient: {
canUserWrite: capabilities.get().canvas.save,
+ zoomScale: 1,
elementStats: {
total: 0,
ready: 0,
diff --git a/x-pack/legacy/plugins/canvas/public/state/reducers/transient.js b/x-pack/legacy/plugins/canvas/public/state/reducers/transient.js
index 3924e08791e25..e059e6439d864 100644
--- a/x-pack/legacy/plugins/canvas/public/state/reducers/transient.js
+++ b/x-pack/legacy/plugins/canvas/public/state/reducers/transient.js
@@ -48,6 +48,13 @@ export const transientReducer = handleActions(
};
},
+ [transientActions.setZoomScale]: (transientState, { payload }) => {
+ return {
+ ...transientState,
+ zoomScale: payload || 1,
+ };
+ },
+
[pageActions.setPage]: transientState => {
return { ...transientState, selectedToplevelNodes: [] };
},
diff --git a/x-pack/legacy/plugins/canvas/public/state/selectors/app.js b/x-pack/legacy/plugins/canvas/public/state/selectors/app.js
index 0f13df85a0353..4df40300c2968 100644
--- a/x-pack/legacy/plugins/canvas/public/state/selectors/app.js
+++ b/x-pack/legacy/plugins/canvas/public/state/selectors/app.js
@@ -15,6 +15,10 @@ export function getFullscreen(state) {
return get(state, 'transient.fullscreen', false);
}
+export function getZoomScale(state) {
+ return get(state, 'transient.zoomScale', 1);
+}
+
export function getServerFunctions(state) {
return get(state, 'app.serverFunctions');
}
diff --git a/x-pack/legacy/plugins/canvas/public/style/main.scss b/x-pack/legacy/plugins/canvas/public/style/main.scss
index ef3057ca539a6..84a06813de282 100644
--- a/x-pack/legacy/plugins/canvas/public/style/main.scss
+++ b/x-pack/legacy/plugins/canvas/public/style/main.scss
@@ -36,6 +36,10 @@ $canvasElementCardWidth: 210px;
max-width: 100%;
}
+.canvasContextMenu--topBorder {
+ border-top: $euiBorderThin;
+}
+
#canvas-app {
overflow-y: hidden;