mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
* feat: add autoplay redux boilerplate WIP auto-play settings * feat: add page cycle settings * feat: add cycle toggle hotkey * chore: add tooltip text to settings icon * settings layout * fix: handle invalid input for custom interval * chore: address nit
This commit is contained in:
parent
a2f116f749
commit
3b693993e7
15 changed files with 384 additions and 106 deletions
|
@ -4,16 +4,13 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexGrid,
|
EuiFlexGrid,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiFormRow,
|
|
||||||
EuiButton,
|
|
||||||
EuiLink,
|
EuiLink,
|
||||||
EuiFieldText,
|
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiHorizontalRule,
|
EuiHorizontalRule,
|
||||||
EuiDescriptionList,
|
EuiDescriptionList,
|
||||||
|
@ -21,24 +18,16 @@ import {
|
||||||
EuiDescriptionListDescription,
|
EuiDescriptionListDescription,
|
||||||
EuiFormLabel,
|
EuiFormLabel,
|
||||||
EuiText,
|
EuiText,
|
||||||
|
EuiButtonIcon,
|
||||||
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { timeDurationString } from '../../../lib/time_duration';
|
import { timeDurationString } from '../../../lib/time_duration';
|
||||||
import { RefreshControl } from '../refresh_control';
|
import { RefreshControl } from '../refresh_control';
|
||||||
|
import { CustomInterval } from './custom_interval';
|
||||||
|
|
||||||
const ListGroup = ({ children }) => <ul style={{ listStyle: 'none', margin: 0 }}>{[children]}</ul>;
|
const ListGroup = ({ children }) => <ul style={{ listStyle: 'none', margin: 0 }}>{[children]}</ul>;
|
||||||
|
|
||||||
export class AutoRefreshControls extends Component {
|
export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterval }) => {
|
||||||
static propTypes = {
|
|
||||||
refreshInterval: PropTypes.number,
|
|
||||||
setRefresh: PropTypes.func.isRequired,
|
|
||||||
disableInterval: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
refreshInput = null;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { refreshInterval, setRefresh, disableInterval } = this.props;
|
|
||||||
|
|
||||||
const RefreshItem = ({ duration, label }) => (
|
const RefreshItem = ({ duration, label }) => (
|
||||||
<li>
|
<li>
|
||||||
<EuiLink onClick={() => setRefresh(duration)}>{label}</EuiLink>
|
<EuiLink onClick={() => setRefresh(duration)}>{label}</EuiLink>
|
||||||
|
@ -46,7 +35,8 @@ export class AutoRefreshControls extends Component {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
<EuiFlexGroup alignItems="center" justifyContent="spaceAround" gutterSize="xs">
|
<EuiFlexGroup alignItems="center" justifyContent="spaceAround" gutterSize="xs">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiDescriptionList textStyle="reverse">
|
<EuiDescriptionList textStyle="reverse">
|
||||||
|
@ -55,11 +45,6 @@ export class AutoRefreshControls extends Component {
|
||||||
{refreshInterval > 0 ? (
|
{refreshInterval > 0 ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<span>Every {timeDurationString(refreshInterval)}</span>
|
<span>Every {timeDurationString(refreshInterval)}</span>
|
||||||
<div>
|
|
||||||
<EuiLink size="s" onClick={disableInterval}>
|
|
||||||
Disable auto-refresh
|
|
||||||
</EuiLink>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : (
|
) : (
|
||||||
<span>Manually</span>
|
<span>Manually</span>
|
||||||
|
@ -67,10 +52,25 @@ export class AutoRefreshControls extends Component {
|
||||||
</EuiDescriptionListDescription>
|
</EuiDescriptionListDescription>
|
||||||
</EuiDescriptionList>
|
</EuiDescriptionList>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiFlexGroup justifyContent="flexEnd" gutterSize="xs">
|
||||||
|
{refreshInterval > 0 ? (
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiToolTip position="bottom" content="Disable auto-refresh">
|
||||||
|
<EuiButtonIcon
|
||||||
|
iconType="cross"
|
||||||
|
onClick={disableInterval}
|
||||||
|
aria-label="Disable auto-refresh"
|
||||||
|
/>
|
||||||
|
</EuiToolTip>
|
||||||
|
</EuiFlexItem>
|
||||||
|
) : null}
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<RefreshControl />
|
<RefreshControl />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
|
||||||
<EuiHorizontalRule margin="m" />
|
<EuiHorizontalRule margin="m" />
|
||||||
|
|
||||||
|
@ -100,35 +100,17 @@ export class AutoRefreshControls extends Component {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGrid>
|
</EuiFlexGrid>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
|
|
||||||
<form
|
|
||||||
onSubmit={ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
setRefresh(this.refreshInput.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EuiFlexGroup gutterSize="s">
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFormRow
|
|
||||||
label="Set a custom interval"
|
|
||||||
helpText="Use shorthand notation, like 30s, 10m, or 1h"
|
|
||||||
compressed
|
|
||||||
>
|
|
||||||
<EuiFieldText inputRef={i => (this.refreshInput = i)} />
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiFormRow label=" ">
|
<CustomInterval onSubmit={value => setRefresh(value)} />
|
||||||
<EuiButton size="s" type="submit" style={{ minWidth: 'auto' }}>
|
|
||||||
Set
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
AutoRefreshControls.propTypes = {
|
||||||
|
refreshInterval: PropTypes.number,
|
||||||
|
setRefresh: PropTypes.func.isRequired,
|
||||||
|
disableInterval: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
|
@ -6,45 +6,29 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 { Popover } from '../../popover';
|
||||||
import { AutoRefreshControls } from './auto_refresh_controls';
|
import { AutoRefreshControls } from './auto_refresh_controls';
|
||||||
|
import { KioskControls } from './kiosk_controls';
|
||||||
|
|
||||||
const getRefreshInterval = (val = '') => {
|
export const ControlSettings = ({
|
||||||
// if it's a number, just use it directly
|
setRefreshInterval,
|
||||||
if (!isNaN(Number(val))) {
|
refreshInterval,
|
||||||
return val;
|
autoplayEnabled,
|
||||||
}
|
autoplayInterval,
|
||||||
|
enableAutoplay,
|
||||||
// if it's a string, try to parse out the shorthand duration value
|
setAutoplayInterval,
|
||||||
const match = String(val).match(/^([0-9]{1,})([hmsd])$/);
|
}) => {
|
||||||
|
const setRefresh = val => setRefreshInterval(val);
|
||||||
// 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));
|
|
||||||
|
|
||||||
const disableInterval = () => {
|
const disableInterval = () => {
|
||||||
setRefresh(0);
|
setRefresh(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const popoverButton = handleClick => (
|
const popoverButton = handleClick => (
|
||||||
|
<EuiToolTip position="bottom" content="Control settings">
|
||||||
<EuiButtonIcon iconType="gear" aria-label="Control settings" onClick={handleClick} />
|
<EuiButtonIcon iconType="gear" aria-label="Control settings" onClick={handleClick} />
|
||||||
|
</EuiToolTip>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -54,19 +38,21 @@ export const ControlSettings = ({ setRefreshInterval, refreshInterval }) => {
|
||||||
anchorPosition="rightUp"
|
anchorPosition="rightUp"
|
||||||
panelClassName="canvasControlSettings__popover"
|
panelClassName="canvasControlSettings__popover"
|
||||||
>
|
>
|
||||||
{({ closePopover }) => (
|
{() => (
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<AutoRefreshControls
|
<AutoRefreshControls
|
||||||
refreshInterval={refreshInterval}
|
refreshInterval={refreshInterval}
|
||||||
setRefresh={val => {
|
setRefresh={val => setRefresh(val)}
|
||||||
setRefresh(val);
|
disableInterval={() => disableInterval()}
|
||||||
closePopover();
|
/>
|
||||||
}}
|
</EuiFlexItem>
|
||||||
disableInterval={() => {
|
<EuiFlexItem>
|
||||||
disableInterval();
|
<KioskControls
|
||||||
closePopover();
|
autoplayEnabled={autoplayEnabled}
|
||||||
}}
|
autoplayInterval={autoplayInterval}
|
||||||
|
onSetInterval={setAutoplayInterval}
|
||||||
|
onSetEnabled={enableAutoplay}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
@ -78,4 +64,8 @@ export const ControlSettings = ({ setRefreshInterval, refreshInterval }) => {
|
||||||
ControlSettings.propTypes = {
|
ControlSettings.propTypes = {
|
||||||
refreshInterval: PropTypes.number,
|
refreshInterval: PropTypes.number,
|
||||||
setRefreshInterval: PropTypes.func.isRequired,
|
setRefreshInterval: PropTypes.func.isRequired,
|
||||||
|
autoplayEnabled: PropTypes.bool,
|
||||||
|
autoplayInterval: PropTypes.number,
|
||||||
|
enableAutoplay: PropTypes.func.isRequired,
|
||||||
|
setAutoplayInterval: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.canvasControlSettings__popover {
|
.canvasControlSettings__popover {
|
||||||
width: 300px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
||||||
|
<form
|
||||||
|
onSubmit={ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
onSubmit(refreshInterval);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EuiFlexGroup gutterSize={gutterSize}>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFormRow
|
||||||
|
label="Set a custom interval"
|
||||||
|
helpText="Use shorthand notation, like 30s, 10m, or 1h"
|
||||||
|
compressed
|
||||||
|
>
|
||||||
|
<EuiFieldText isInvalid={isInvalid} value={customInterval} onChange={handleChange} />
|
||||||
|
</EuiFormRow>
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiFormRow label=" ">
|
||||||
|
<EuiButton
|
||||||
|
disabled={isInvalid}
|
||||||
|
size={buttonSize}
|
||||||
|
type="submit"
|
||||||
|
style={{ minWidth: 'auto' }}
|
||||||
|
>
|
||||||
|
Set
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFormRow>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomInterval.propTypes = {
|
||||||
|
buttonSize: PropTypes.string,
|
||||||
|
gutterSize: PropTypes.string,
|
||||||
|
defaultValue: PropTypes.string,
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomInterval.defaultProps = {
|
||||||
|
buttonSize: 's',
|
||||||
|
gutterSize: 's',
|
||||||
|
defaultValue: '',
|
||||||
|
};
|
|
@ -5,16 +5,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { setRefreshInterval } from '../../../state/actions/workpad';
|
import {
|
||||||
import { getRefreshInterval } from '../../../state/selectors/workpad';
|
setRefreshInterval,
|
||||||
|
enableAutoplay,
|
||||||
|
setAutoplayInterval,
|
||||||
|
} from '../../../state/actions/workpad';
|
||||||
|
import { getRefreshInterval, getAutoplay } from '../../../state/selectors/workpad';
|
||||||
import { ControlSettings as Component } from './control_settings';
|
import { ControlSettings as Component } from './control_settings';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => {
|
||||||
|
const { enabled, interval } = getAutoplay(state);
|
||||||
|
|
||||||
|
return {
|
||||||
refreshInterval: getRefreshInterval(state),
|
refreshInterval: getRefreshInterval(state),
|
||||||
});
|
autoplayEnabled: enabled,
|
||||||
|
autoplayInterval: interval,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
setRefreshInterval,
|
setRefreshInterval,
|
||||||
|
enableAutoplay,
|
||||||
|
setAutoplayInterval,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlSettings = connect(
|
export const ControlSettings = connect(
|
||||||
|
|
|
@ -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 }) => <ul style={{ listStyle: 'none', margin: 0 }}>{[children]}</ul>;
|
||||||
|
|
||||||
|
export const KioskControls = ({
|
||||||
|
autoplayEnabled,
|
||||||
|
autoplayInterval,
|
||||||
|
onSetEnabled,
|
||||||
|
onSetInterval,
|
||||||
|
}) => {
|
||||||
|
const RefreshItem = ({ duration, label }) => (
|
||||||
|
<li>
|
||||||
|
<EuiLink onClick={() => onSetInterval(duration)}>{label}</EuiLink>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiDescriptionList textStyle="reverse">
|
||||||
|
<EuiDescriptionListTitle>Cycle fullscreen pages</EuiDescriptionListTitle>
|
||||||
|
<EuiDescriptionListDescription>
|
||||||
|
<span>Every {timeDurationString(autoplayInterval)}</span>
|
||||||
|
</EuiDescriptionListDescription>
|
||||||
|
</EuiDescriptionList>
|
||||||
|
<EuiHorizontalRule margin="m" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<EuiSwitch
|
||||||
|
checked={autoplayEnabled}
|
||||||
|
label="Cycle slides automatically"
|
||||||
|
onChange={ev => onSetEnabled(ev.target.checked)}
|
||||||
|
/>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EuiFormLabel>Change cycling interval</EuiFormLabel>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
|
||||||
|
<EuiText size="s">
|
||||||
|
<EuiFlexGrid gutterSize="s" columns={2}>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<ListGroup>
|
||||||
|
<RefreshItem duration="5000" label="5 seconds" />
|
||||||
|
<RefreshItem duration="10000" label="10 seconds" />
|
||||||
|
<RefreshItem duration="30000" label="30 seconds" />
|
||||||
|
</ListGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<ListGroup>
|
||||||
|
<RefreshItem duration="60000" label="1 minute" />
|
||||||
|
<RefreshItem duration="300000" label="5 minutes" />
|
||||||
|
<RefreshItem duration="900000" label="15 minute" />
|
||||||
|
</ListGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGrid>
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<CustomInterval onSubmit={value => onSetInterval(value)} />
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
KioskControls.propTypes = {
|
||||||
|
autoplayEnabled: PropTypes.bool.isRequired,
|
||||||
|
autoplayInterval: PropTypes.number.isRequired,
|
||||||
|
onSetEnabled: PropTypes.func.isRequired,
|
||||||
|
onSetInterval: PropTypes.func.isRequired,
|
||||||
|
};
|
|
@ -16,6 +16,10 @@ export class FullscreenControl extends React.PureComponent {
|
||||||
if (enterFullscreen || exitFullscreen) {
|
if (enterFullscreen || exitFullscreen) {
|
||||||
this.toggleFullscreen();
|
this.toggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === 'PAGE_CYCLE_TOGGLE') {
|
||||||
|
this.props.enableAutoplay(!this.props.autoplayEnabled);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFullscreen = () => {
|
toggleFullscreen = () => {
|
||||||
|
|
|
@ -6,11 +6,14 @@
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
|
import { setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
|
||||||
|
import { enableAutoplay } from '../../../state/actions/workpad';
|
||||||
import { getFullscreen } from '../../../state/selectors/app';
|
import { getFullscreen } from '../../../state/selectors/app';
|
||||||
|
import { getAutoplay } from '../../../state/selectors/workpad';
|
||||||
import { FullscreenControl as Component } from './fullscreen_control';
|
import { FullscreenControl as Component } from './fullscreen_control';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
isFullscreen: getFullscreen(state),
|
isFullscreen: getFullscreen(state),
|
||||||
|
autoplayEnabled: getAutoplay(state).enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
@ -18,6 +21,7 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(setFullscreen(value));
|
dispatch(setFullscreen(value));
|
||||||
value && dispatch(selectToplevelNodes([]));
|
value && dispatch(selectToplevelNodes([]));
|
||||||
},
|
},
|
||||||
|
enableAutoplay: enabled => dispatch(enableAutoplay(enabled)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FullscreenControl = connect(
|
export const FullscreenControl = connect(
|
||||||
|
|
|
@ -53,6 +53,7 @@ const deleteElementShortcuts = ['del', 'backspace'];
|
||||||
const groupShortcut = ['g'];
|
const groupShortcut = ['g'];
|
||||||
const ungroupShortcut = ['u'];
|
const ungroupShortcut = ['u'];
|
||||||
const fullscreentExitShortcut = ['esc'];
|
const fullscreentExitShortcut = ['esc'];
|
||||||
|
const fullscreenPageCycle = ['p'];
|
||||||
|
|
||||||
export const keymap = {
|
export const keymap = {
|
||||||
ELEMENT: {
|
ELEMENT: {
|
||||||
|
@ -120,6 +121,7 @@ export const keymap = {
|
||||||
key === 'help' ? osShortcuts : osShortcuts.concat(['space', 'right'])
|
key === 'help' ? osShortcuts : osShortcuts.concat(['space', 'right'])
|
||||||
),
|
),
|
||||||
REFRESH: refreshShortcut,
|
REFRESH: refreshShortcut,
|
||||||
|
PAGE_CYCLE_TOGGLE: { ...getShortcuts(fullscreenPageCycle), help: 'Toggle page cycling' },
|
||||||
},
|
},
|
||||||
EXPRESSION: {
|
EXPRESSION: {
|
||||||
displayName: 'Expression controls',
|
displayName: 'Expression controls',
|
||||||
|
|
|
@ -16,6 +16,8 @@ export const setWriteable = createAction('setWriteable');
|
||||||
export const setColors = createAction('setColors');
|
export const setColors = createAction('setColors');
|
||||||
export const setRefreshInterval = createAction('setRefreshInterval');
|
export const setRefreshInterval = createAction('setRefreshInterval');
|
||||||
export const setWorkpadCSS = createAction('setWorkpadCSS');
|
export const setWorkpadCSS = createAction('setWorkpadCSS');
|
||||||
|
export const enableAutoplay = createAction('enableAutoplay');
|
||||||
|
export const setAutoplayInterval = createAction('setAutoplayInterval');
|
||||||
|
|
||||||
export const initializeWorkpad = createThunk('initializeWorkpad', ({ dispatch }) => {
|
export const initializeWorkpad = createThunk('initializeWorkpad', ({ dispatch }) => {
|
||||||
dispatch(fetchAllRenderables());
|
dispatch(fetchAllRenderables());
|
||||||
|
|
|
@ -26,6 +26,10 @@ export const getInitialState = path => {
|
||||||
refresh: {
|
refresh: {
|
||||||
interval: 0,
|
interval: 0,
|
||||||
},
|
},
|
||||||
|
autoplay: {
|
||||||
|
enabled: false,
|
||||||
|
interval: 10000,
|
||||||
|
},
|
||||||
// values in resolvedArgs should live under a unique index so they can be looked up.
|
// values in resolvedArgs should live under a unique index so they can be looked up.
|
||||||
// The ID of the element is a great example.
|
// 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.
|
// In there will live an object with a status (string), value (any), and error (Error) property.
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { historyMiddleware } from './history';
|
||||||
import { inFlight } from './in_flight';
|
import { inFlight } from './in_flight';
|
||||||
import { workpadUpdate } from './workpad_update';
|
import { workpadUpdate } from './workpad_update';
|
||||||
import { workpadRefresh } from './workpad_refresh';
|
import { workpadRefresh } from './workpad_refresh';
|
||||||
|
import { workpadAutoplay } from './workpad_autoplay';
|
||||||
import { appReady } from './app_ready';
|
import { appReady } from './app_ready';
|
||||||
import { elementStats } from './element_stats';
|
import { elementStats } from './element_stats';
|
||||||
import { resolvedArgs } from './resolved_args';
|
import { resolvedArgs } from './resolved_args';
|
||||||
|
@ -30,7 +31,8 @@ const middlewares = [
|
||||||
inFlight,
|
inFlight,
|
||||||
appReady,
|
appReady,
|
||||||
workpadUpdate,
|
workpadUpdate,
|
||||||
workpadRefresh
|
workpadRefresh,
|
||||||
|
workpadAutoplay
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -10,7 +10,7 @@ import { restoreHistory } from '../actions/history';
|
||||||
import * as pageActions from '../actions/pages';
|
import * as pageActions from '../actions/pages';
|
||||||
import * as transientActions from '../actions/transient';
|
import * as transientActions from '../actions/transient';
|
||||||
import { removeElements } from '../actions/elements';
|
import { removeElements } from '../actions/elements';
|
||||||
import { setRefreshInterval } from '../actions/workpad';
|
import { setRefreshInterval, enableAutoplay, setAutoplayInterval } from '../actions/workpad';
|
||||||
|
|
||||||
export const transientReducer = handleActions(
|
export const transientReducer = handleActions(
|
||||||
{
|
{
|
||||||
|
@ -63,6 +63,14 @@ export const transientReducer = handleActions(
|
||||||
[setRefreshInterval]: (transientState, { payload }) => {
|
[setRefreshInterval]: (transientState, { payload }) => {
|
||||||
return { ...transientState, refresh: { interval: Number(payload) || 0 } };
|
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);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
|
@ -240,3 +240,7 @@ export function getContextForIndex(state, index) {
|
||||||
export function getRefreshInterval(state) {
|
export function getRefreshInterval(state) {
|
||||||
return get(state, 'transient.refresh.interval', 0);
|
return get(state, 'transient.refresh.interval', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAutoplay(state) {
|
||||||
|
return get(state, 'transient.autoplay');
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue