diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.js b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.js
index 68d8143230d6e..f8a7c0edeb4c1 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.js
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.js
@@ -4,16 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, Component } from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiFlexGroup,
EuiFlexGrid,
EuiFlexItem,
- EuiFormRow,
- EuiButton,
EuiLink,
- EuiFieldText,
EuiSpacer,
EuiHorizontalRule,
EuiDescriptionList,
@@ -21,32 +18,25 @@ import {
EuiDescriptionListDescription,
EuiFormLabel,
EuiText,
+ EuiButtonIcon,
+ EuiToolTip,
} from '@elastic/eui';
import { timeDurationString } from '../../../lib/time_duration';
import { RefreshControl } from '../refresh_control';
+import { CustomInterval } from './custom_interval';
const ListGroup = ({ children }) =>
;
-export class AutoRefreshControls extends Component {
- static propTypes = {
- refreshInterval: PropTypes.number,
- setRefresh: PropTypes.func.isRequired,
- disableInterval: PropTypes.func.isRequired,
- };
+export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterval }) => {
+ const RefreshItem = ({ duration, label }) => (
+
+ setRefresh(duration)}>{label}
+
+ );
- refreshInput = null;
-
- render() {
- const { refreshInterval, setRefresh, disableInterval } = this.props;
-
- const RefreshItem = ({ duration, label }) => (
-
- setRefresh(duration)}>{label}
-
- );
-
- return (
-
+ return (
+
+
@@ -55,11 +45,6 @@ export class AutoRefreshControls extends Component {
{refreshInterval > 0 ? (
Every {timeDurationString(refreshInterval)}
-
-
- Disable auto-refresh
-
-
) : (
Manually
@@ -68,7 +53,22 @@ export class AutoRefreshControls extends Component {
-
+
+ {refreshInterval > 0 ? (
+
+
+
+
+
+ ) : null}
+
+
+
+
@@ -100,35 +100,17 @@ export class AutoRefreshControls extends Component {
+
-
+
+ setRefresh(value)} />
+
+
+ );
+};
-
-
- );
- }
-}
+AutoRefreshControls.propTypes = {
+ refreshInterval: PropTypes.number,
+ setRefresh: PropTypes.func.isRequired,
+ disableInterval: PropTypes.func.isRequired,
+};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.js b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.js
index 8801c198ffbbb..4e73fca88fec4 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.js
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.js
@@ -6,45 +6,29 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { Popover } from '../../popover';
import { AutoRefreshControls } from './auto_refresh_controls';
+import { KioskControls } from './kiosk_controls';
-const getRefreshInterval = (val = '') => {
- // if it's a number, just use it directly
- if (!isNaN(Number(val))) {
- return val;
- }
-
- // if it's a string, try to parse out the shorthand duration value
- const match = String(val).match(/^([0-9]{1,})([hmsd])$/);
-
- // TODO: do something better with improper input, like show an error...
- if (!match) {
- return;
- }
-
- switch (match[2]) {
- case 's':
- return match[1] * 1000;
- case 'm':
- return match[1] * 1000 * 60;
- case 'h':
- return match[1] * 1000 * 60 * 60;
- case 'd':
- return match[1] * 1000 * 60 * 60 * 24;
- }
-};
-
-export const ControlSettings = ({ setRefreshInterval, refreshInterval }) => {
- const setRefresh = val => setRefreshInterval(getRefreshInterval(val));
+export const ControlSettings = ({
+ setRefreshInterval,
+ refreshInterval,
+ autoplayEnabled,
+ autoplayInterval,
+ enableAutoplay,
+ setAutoplayInterval,
+}) => {
+ const setRefresh = val => setRefreshInterval(val);
const disableInterval = () => {
setRefresh(0);
};
const popoverButton = handleClick => (
-
+
+
+
);
return (
@@ -54,19 +38,21 @@ export const ControlSettings = ({ setRefreshInterval, refreshInterval }) => {
anchorPosition="rightUp"
panelClassName="canvasControlSettings__popover"
>
- {({ closePopover }) => (
+ {() => (
{
- setRefresh(val);
- closePopover();
- }}
- disableInterval={() => {
- disableInterval();
- closePopover();
- }}
+ setRefresh={val => setRefresh(val)}
+ disableInterval={() => disableInterval()}
+ />
+
+
+
@@ -78,4 +64,8 @@ export const ControlSettings = ({ setRefreshInterval, refreshInterval }) => {
ControlSettings.propTypes = {
refreshInterval: PropTypes.number,
setRefreshInterval: PropTypes.func.isRequired,
+ autoplayEnabled: PropTypes.bool,
+ autoplayInterval: PropTypes.number,
+ enableAutoplay: PropTypes.func.isRequired,
+ setAutoplayInterval: PropTypes.func.isRequired,
};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss
index c481c59dac160..beaead8b99fc3 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss
@@ -1,3 +1,3 @@
.canvasControlSettings__popover {
- width: 300px;
+ width: 600px;
}
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/custom_interval.js b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/custom_interval.js
new file mode 100644
index 0000000000000..2e2bf0722f5a1
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/custom_interval.js
@@ -0,0 +1,90 @@
+/*
+ * 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, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiButton, EuiFieldText } from '@elastic/eui';
+
+const getRefreshInterval = (val = '') => {
+ // if it's a number, there is no interval, return undefined
+ if (!isNaN(Number(val))) {
+ return;
+ }
+
+ // if it's a string, try to parse out the shorthand duration value
+ const match = String(val).match(/^([0-9]{1,})([hmsd])$/);
+
+ // if it's invalid, there is no interval, return undefined
+ if (!match) {
+ return;
+ }
+
+ switch (match[2]) {
+ case 's':
+ return match[1] * 1000;
+ case 'm':
+ return match[1] * 1000 * 60;
+ case 'h':
+ return match[1] * 1000 * 60 * 60;
+ case 'd':
+ return match[1] * 1000 * 60 * 60 * 24;
+ }
+};
+
+export const CustomInterval = ({ gutterSize, buttonSize, onSubmit, defaultValue }) => {
+ const [customInterval, setCustomInterval] = useState(defaultValue);
+ const refreshInterval = getRefreshInterval(customInterval);
+ const isInvalid = Boolean(customInterval.length && !refreshInterval);
+
+ const handleChange = ev => setCustomInterval(ev.target.value);
+
+ return (
+
+ );
+};
+
+CustomInterval.propTypes = {
+ buttonSize: PropTypes.string,
+ gutterSize: PropTypes.string,
+ defaultValue: PropTypes.string,
+ onSubmit: PropTypes.func.isRequired,
+};
+
+CustomInterval.defaultProps = {
+ buttonSize: 's',
+ gutterSize: 's',
+ defaultValue: '',
+};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/index.js b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/index.js
index 90e127582fecc..ef8fc10a24431 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/index.js
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/index.js
@@ -5,16 +5,28 @@
*/
import { connect } from 'react-redux';
-import { setRefreshInterval } from '../../../state/actions/workpad';
-import { getRefreshInterval } from '../../../state/selectors/workpad';
+import {
+ setRefreshInterval,
+ enableAutoplay,
+ setAutoplayInterval,
+} from '../../../state/actions/workpad';
+import { getRefreshInterval, getAutoplay } from '../../../state/selectors/workpad';
import { ControlSettings as Component } from './control_settings';
-const mapStateToProps = state => ({
- refreshInterval: getRefreshInterval(state),
-});
+const mapStateToProps = state => {
+ const { enabled, interval } = getAutoplay(state);
+
+ return {
+ refreshInterval: getRefreshInterval(state),
+ autoplayEnabled: enabled,
+ autoplayInterval: interval,
+ };
+};
const mapDispatchToProps = {
setRefreshInterval,
+ enableAutoplay,
+ setAutoplayInterval,
};
export const ControlSettings = connect(
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.js b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.js
new file mode 100644
index 0000000000000..27a365ec2572a
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.js
@@ -0,0 +1,95 @@
+/*
+ * 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 {
+ EuiDescriptionList,
+ EuiDescriptionListDescription,
+ EuiDescriptionListTitle,
+ EuiFormLabel,
+ EuiHorizontalRule,
+ EuiLink,
+ EuiSpacer,
+ EuiSwitch,
+ EuiText,
+ EuiFlexGrid,
+ EuiFlexItem,
+ EuiFlexGroup,
+} from '@elastic/eui';
+import { timeDurationString } from '../../../lib/time_duration';
+import { CustomInterval } from './custom_interval';
+
+const ListGroup = ({ children }) => ;
+
+export const KioskControls = ({
+ autoplayEnabled,
+ autoplayInterval,
+ onSetEnabled,
+ onSetInterval,
+}) => {
+ const RefreshItem = ({ duration, label }) => (
+
+ onSetInterval(duration)}>{label}
+
+ );
+
+ return (
+
+
+
+ Cycle fullscreen pages
+
+ Every {timeDurationString(autoplayInterval)}
+
+
+
+
+
+ onSetEnabled(ev.target.checked)}
+ />
+
+
+
+ Change cycling interval
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ onSetInterval(value)} />
+
+
+ );
+};
+
+KioskControls.propTypes = {
+ autoplayEnabled: PropTypes.bool.isRequired,
+ autoplayInterval: PropTypes.number.isRequired,
+ onSetEnabled: PropTypes.func.isRequired,
+ onSetInterval: PropTypes.func.isRequired,
+};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.js b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.js
index 4e2bc84deef7d..a80f120874151 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.js
+++ b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.js
@@ -16,6 +16,10 @@ export class FullscreenControl extends React.PureComponent {
if (enterFullscreen || exitFullscreen) {
this.toggleFullscreen();
}
+
+ if (action === 'PAGE_CYCLE_TOGGLE') {
+ this.props.enableAutoplay(!this.props.autoplayEnabled);
+ }
};
toggleFullscreen = () => {
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js
index 31b0e97eee438..088eaafd3310f 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js
+++ b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js
@@ -6,11 +6,14 @@
import { connect } from 'react-redux';
import { setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
+import { enableAutoplay } from '../../../state/actions/workpad';
import { getFullscreen } from '../../../state/selectors/app';
+import { getAutoplay } from '../../../state/selectors/workpad';
import { FullscreenControl as Component } from './fullscreen_control';
const mapStateToProps = state => ({
isFullscreen: getFullscreen(state),
+ autoplayEnabled: getAutoplay(state).enabled,
});
const mapDispatchToProps = dispatch => ({
@@ -18,6 +21,7 @@ const mapDispatchToProps = dispatch => ({
dispatch(setFullscreen(value));
value && dispatch(selectToplevelNodes([]));
},
+ enableAutoplay: enabled => dispatch(enableAutoplay(enabled)),
});
export const FullscreenControl = connect(
diff --git a/x-pack/plugins/canvas/public/lib/keymap.js b/x-pack/plugins/canvas/public/lib/keymap.js
index 80ea05de7269f..6b3e59428cf3e 100644
--- a/x-pack/plugins/canvas/public/lib/keymap.js
+++ b/x-pack/plugins/canvas/public/lib/keymap.js
@@ -53,6 +53,7 @@ const deleteElementShortcuts = ['del', 'backspace'];
const groupShortcut = ['g'];
const ungroupShortcut = ['u'];
const fullscreentExitShortcut = ['esc'];
+const fullscreenPageCycle = ['p'];
export const keymap = {
ELEMENT: {
@@ -120,6 +121,7 @@ export const keymap = {
key === 'help' ? osShortcuts : osShortcuts.concat(['space', 'right'])
),
REFRESH: refreshShortcut,
+ PAGE_CYCLE_TOGGLE: { ...getShortcuts(fullscreenPageCycle), help: 'Toggle page cycling' },
},
EXPRESSION: {
displayName: 'Expression controls',
diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.js b/x-pack/plugins/canvas/public/state/actions/workpad.js
index af9206018c297..a207488495740 100644
--- a/x-pack/plugins/canvas/public/state/actions/workpad.js
+++ b/x-pack/plugins/canvas/public/state/actions/workpad.js
@@ -16,6 +16,8 @@ export const setWriteable = createAction('setWriteable');
export const setColors = createAction('setColors');
export const setRefreshInterval = createAction('setRefreshInterval');
export const setWorkpadCSS = createAction('setWorkpadCSS');
+export const enableAutoplay = createAction('enableAutoplay');
+export const setAutoplayInterval = createAction('setAutoplayInterval');
export const initializeWorkpad = createThunk('initializeWorkpad', ({ dispatch }) => {
dispatch(fetchAllRenderables());
diff --git a/x-pack/plugins/canvas/public/state/initial_state.js b/x-pack/plugins/canvas/public/state/initial_state.js
index 0c03762cc56d3..2ba4cd1c37f36 100644
--- a/x-pack/plugins/canvas/public/state/initial_state.js
+++ b/x-pack/plugins/canvas/public/state/initial_state.js
@@ -26,6 +26,10 @@ export const getInitialState = path => {
refresh: {
interval: 0,
},
+ autoplay: {
+ enabled: false,
+ interval: 10000,
+ },
// values in resolvedArgs should live under a unique index so they can be looked up.
// The ID of the element is a great example.
// In there will live an object with a status (string), value (any), and error (Error) property.
diff --git a/x-pack/plugins/canvas/public/state/middleware/index.js b/x-pack/plugins/canvas/public/state/middleware/index.js
index 88481c4682eb8..04a77ca06792a 100644
--- a/x-pack/plugins/canvas/public/state/middleware/index.js
+++ b/x-pack/plugins/canvas/public/state/middleware/index.js
@@ -14,6 +14,7 @@ import { historyMiddleware } from './history';
import { inFlight } from './in_flight';
import { workpadUpdate } from './workpad_update';
import { workpadRefresh } from './workpad_refresh';
+import { workpadAutoplay } from './workpad_autoplay';
import { appReady } from './app_ready';
import { elementStats } from './element_stats';
import { resolvedArgs } from './resolved_args';
@@ -30,7 +31,8 @@ const middlewares = [
inFlight,
appReady,
workpadUpdate,
- workpadRefresh
+ workpadRefresh,
+ workpadAutoplay
),
];
diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.js b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.js
new file mode 100644
index 0000000000000..75da7807944ba
--- /dev/null
+++ b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.js
@@ -0,0 +1,79 @@
+/*
+ * 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 { inFlightComplete } from '../actions/resolved_args';
+import { getFullscreen } from '../selectors/app';
+import { getInFlight } from '../selectors/resolved_args';
+import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad';
+import { routerProvider } from '../../lib/router_provider';
+
+export const workpadAutoplay = ({ getState }) => next => {
+ let playTimeout;
+ let displayInterval = 0;
+
+ const router = routerProvider();
+
+ function updateWorkpad() {
+ if (displayInterval === 0) {
+ return;
+ }
+
+ // check the request in flight status
+ const inFlightActive = getInFlight(getState());
+
+ // only navigate if no requests are in-flight
+ if (!inFlightActive) {
+ // update the elements on the workpad
+ const workpadId = getWorkpad(getState()).id;
+ const pageIndex = getSelectedPageIndex(getState());
+ const pageCount = getPages(getState()).length;
+ const nextPage = Math.min(pageIndex + 1, pageCount - 1);
+
+ // go to start if on the last page
+ if (nextPage === pageIndex) {
+ router.navigateTo('loadWorkpad', { id: workpadId, page: 1 });
+ } else {
+ router.navigateTo('loadWorkpad', { id: workpadId, page: nextPage + 1 });
+ }
+ }
+
+ startDelayedUpdate();
+ }
+
+ function stopAutoUpdate() {
+ clearTimeout(playTimeout); // cancel any pending update requests
+ }
+
+ function startDelayedUpdate() {
+ stopAutoUpdate();
+ playTimeout = setTimeout(() => {
+ updateWorkpad();
+ }, displayInterval);
+ }
+
+ return action => {
+ next(action);
+
+ const isFullscreen = getFullscreen(getState());
+ const autoplay = getAutoplay(getState());
+ const shouldPlay = isFullscreen && autoplay.enabled && autoplay.interval > 0;
+ displayInterval = autoplay.interval;
+
+ // when in-flight requests are finished, update the workpad after a given delay
+ if (action.type === inFlightComplete.toString() && shouldPlay) {
+ startDelayedUpdate();
+ } // create new update request
+
+ // This middleware creates or destroys an interval that will cause workpad elements to update
+ // clear any pending timeout
+ stopAutoUpdate();
+
+ // if interval is larger than 0, start the delayed update
+ if (shouldPlay) {
+ startDelayedUpdate();
+ }
+ };
+};
diff --git a/x-pack/plugins/canvas/public/state/reducers/transient.js b/x-pack/plugins/canvas/public/state/reducers/transient.js
index 43b2c9ccc188c..3924e08791e25 100644
--- a/x-pack/plugins/canvas/public/state/reducers/transient.js
+++ b/x-pack/plugins/canvas/public/state/reducers/transient.js
@@ -10,7 +10,7 @@ import { restoreHistory } from '../actions/history';
import * as pageActions from '../actions/pages';
import * as transientActions from '../actions/transient';
import { removeElements } from '../actions/elements';
-import { setRefreshInterval } from '../actions/workpad';
+import { setRefreshInterval, enableAutoplay, setAutoplayInterval } from '../actions/workpad';
export const transientReducer = handleActions(
{
@@ -63,6 +63,14 @@ export const transientReducer = handleActions(
[setRefreshInterval]: (transientState, { payload }) => {
return { ...transientState, refresh: { interval: Number(payload) || 0 } };
},
+
+ [enableAutoplay]: (transientState, { payload }) => {
+ return set(transientState, 'autoplay.enabled', Boolean(payload) || false);
+ },
+
+ [setAutoplayInterval]: (transientState, { payload }) => {
+ return set(transientState, 'autoplay.interval', Number(payload) || 0);
+ },
},
{}
);
diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.js b/x-pack/plugins/canvas/public/state/selectors/workpad.js
index 4bfa748e51fd6..7d9f5181fe9d1 100644
--- a/x-pack/plugins/canvas/public/state/selectors/workpad.js
+++ b/x-pack/plugins/canvas/public/state/selectors/workpad.js
@@ -240,3 +240,7 @@ export function getContextForIndex(state, index) {
export function getRefreshInterval(state) {
return get(state, 'transient.refresh.interval', 0);
}
+
+export function getAutoplay(state) {
+ return get(state, 'transient.autoplay');
+}