Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] add timezone and timeFormat prop for time display in animation control and time widget #1411

Merged
merged 4 commits into from
Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"mapbox": "^1.0.0-beta10",
"mini-svg-data-uri": "^1.0.3",
"moment": "^2.10.6",
"moment-timezone": "^0.5.32",
"pbf": "^3.1.0",
"prop-types": "^15.6.0",
"react-color": "^2.17.3",
Expand Down
1 change: 0 additions & 1 deletion scripts/ts-smoosh/tests/02-imports/c.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.


/**
* A function with a JSDoc type import that is different from its name
* @type {typeof import('./b').MyFn}
Expand Down
23 changes: 23 additions & 0 deletions src/actions/vis-state-actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,26 @@ export type ProcessFileContentUpdaterAction = {
export function processFileContent(
payload: ProcessFileContentUpdaterAction['payload']
): Merge<ProcessFileContentUpdaterAction, {type: ActionTypes.PROCESS_FILE_CONTENT}>;

export type SetLayerAnimationTimeConfigAction = {
config: {
timezone?: string;
timeFormat?: string;
};
};

export function setLayerAnimationTimeConfig(
config: SetLayerAnimationTimeConfigAction['config']
): Merge<SetLayerAnimationTimeConfigAction, {type: ActionTypes.SET_TIME_FORMAT}>;

export type SetFilterAnimationTimeConfigAction = {
idx: number;
config: {
timezone?: string;
timeFormat?: string;
};
}
export function setFilterAnimationTimeConfig(
idx: SetFilterAnimationTimeConfigAction['idx'],
config: SetFilterAnimationTimeConfigAction['config']
): Merge<timeFormatAction, {type: ActionTypes.SET_TIME_FORMAT}>;
30 changes: 30 additions & 0 deletions src/actions/vis-state-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,36 @@ export function processFileContent(payload) {
};
}

/**
* Set layer animation time format and timezone
* @memberof visStateActions
* @param config - {timeFormat: string, timezone: string}
* @type {typeof import('./vis-state-actions').setLayerAnimationTimeConfig}
* @return action
*/
export function setLayerAnimationTimeConfig(config) {
return {
type: ActionTypes.SET_LAYER_ANIMATION_TIME_CONFIG,
config
};
}

/**
* Set Filter animation time format and timezone
* @memberof visStateActions
* @param idx
* @param config
* @type {typeof import('./vis-state-actions').setFilterAnimationTimeConfig}
* @return action
*/
export function setFilterAnimationTimeConfig(idx, config) {
return {
type: ActionTypes.SET_FILTER_ANIMATION_TIME_CONFIG,
idx,
config
};
}

/**
* This declaration is needed to group actions in docs
*/
Expand Down
12 changes: 8 additions & 4 deletions src/components/bottom-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,16 @@ export default function BottomWidgetFactory(
animationConfig={animationConfig}
setLayerAnimationTime={visStateActions.setLayerAnimationTime}
>
{animationControlProps =>
{(isAnimating, start, pause, reset) =>
showAnimationControl ? (
<AnimationControl
animationConfig={animationConfig}
setLayerAnimationTime={visStateActions.setLayerAnimationTime}
updateAnimationSpeed={visStateActions.updateLayerAnimationSpeed}
toggleAnimation={visStateActions.toggleLayerAnimation}
isAnimatable={!animatedFilter}
animationControlProps={animationControlProps}
isAnimating={isAnimating}
resetAnimation={reset}
/>
) : null
}
Expand All @@ -201,9 +202,12 @@ export default function BottomWidgetFactory(
filterIdx={animatedFilterIdx > -1 ? animatedFilterIdx : enlargedFilterIdx}
setFilterAnimationTime={visStateActions.setFilterAnimationTime}
>
{animationControlProps =>
{(isAnimating, start, pause, resetAnimation) =>
showTimeWidget ? (
<TimeWidget
// TimeWidget uses React.memo, here we pass width
// even though it doesnt use it, to force rerender
width={enlargedFilterWidth}
filter={filters[enlargedFilterIdx]}
index={enlargedFilterIdx}
isAnyFilterAnimating={Boolean(animatedFilter)}
Expand All @@ -217,7 +221,7 @@ export default function BottomWidgetFactory(
toggleAnimation={visStateActions.toggleFilterAnimation}
updateAnimationSpeed={visStateActions.updateFilterAnimationSpeed}
enlargeFilter={visStateActions.enlargeFilter}
animationControlProps={animationControlProps}
resetAnimation={resetAnimation}
isAnimatable={!animationConfig || !animationConfig.isAnimating}
/>
) : null
Expand Down
50 changes: 38 additions & 12 deletions src/components/common/animation-control/animation-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {useCallback} from 'react';
import React, {useCallback, useMemo} from 'react';
import styled from 'styled-components';
import moment from 'moment';

import Slider from 'components/common/slider/slider';
import {BottomWidgetInner} from 'components/common/styled-components';
import PlaybackControlsFactory from './playback-controls';
import FloatingTimeDisplayFactory from './floating-time-display';
import {snapToMarks} from 'utils/data-utils';
import {DEFAULT_TIME_FORMAT} from 'constants/default-settings';
import {datetimeFormatter} from 'utils/data-utils';

const SliderWrapper = styled.div`
display: flex;
Expand All @@ -49,7 +49,9 @@ const AnimationWidgetInner = styled.div`
}
`;

const StyledDomain = styled.div`
const StyledDomain = styled.div.attrs({
className: 'animation-control__time-domain'
})`
color: ${props => props.theme.titleTextColor};
font-weight: 400;
font-size: 10px;
Expand All @@ -60,13 +62,23 @@ AnimationControlFactory.deps = [PlaybackControlsFactory, FloatingTimeDisplayFact
function AnimationControlFactory(PlaybackControls, FloatingTimeDisplay) {
const AnimationControl = ({
isAnimatable,
animationControlProps,
isAnimating,
resetAnimation,
toggleAnimation,
setLayerAnimationTime,
updateAnimationSpeed,
animationConfig
}) => {
const {currentTime, domain, speed, step, timeSteps} = animationConfig;
const {
currentTime,
domain,
speed,
step,
timeSteps,
timeFormat,
timezone,
defaultTimeFormat
} = animationConfig;
const onSlider1Change = useCallback(
val => {
if (Array.isArray(timeSteps)) {
Expand All @@ -80,21 +92,30 @@ function AnimationControlFactory(PlaybackControls, FloatingTimeDisplay) {
[domain, timeSteps, setLayerAnimationTime]
);

const dateFunc = useMemo(() => {
const hasUserFormat = typeof timeFormat === 'string';
const currentFormat = (hasUserFormat ? timeFormat : defaultTimeFormat) || DEFAULT_TIME_FORMAT;
return datetimeFormatter(timezone)(currentFormat);
}, [timeFormat, defaultTimeFormat, timezone]);

const timeStart = useMemo(() => (domain ? dateFunc(domain[0]) : ''), [domain, dateFunc]);
const timeEnd = useMemo(() => (domain ? dateFunc(domain[1]) : ''), [domain, dateFunc]);

return (
<BottomWidgetInner className="bottom-widget--inner">
<AnimationWidgetInner className="animation-widget--inner">
<PlaybackControls
className="animation-control-playpause"
startAnimation={toggleAnimation}
isAnimating={animationControlProps.isAnimating}
isAnimating={isAnimating}
pauseAnimation={toggleAnimation}
resetAnimation={animationControlProps.reset}
resetAnimation={resetAnimation}
speed={speed}
isAnimatable={isAnimatable}
updateAnimationSpeed={updateAnimationSpeed}
/>
<StyledDomain className="animation-control__time-domain">
<span>{domain ? moment.utc(domain[0]).format(DEFAULT_TIME_FORMAT) : ''}</span>
<StyledDomain className="domain-start">
<span>{timeStart}</span>
</StyledDomain>
<SliderWrapper className="animation-control__slider">
<Slider
Expand All @@ -108,11 +129,16 @@ function AnimationControlFactory(PlaybackControls, FloatingTimeDisplay) {
enableBarDrag={true}
/>
</SliderWrapper>
<StyledDomain className="animation-control__time-domain">
<span>{domain ? moment.utc(domain[1]).format(DEFAULT_TIME_FORMAT) : ''}</span>
<StyledDomain className="domain-end">
<span>{timeEnd}</span>
</StyledDomain>
</AnimationWidgetInner>
<FloatingTimeDisplay currentTime={currentTime} />
<FloatingTimeDisplay
currentTime={currentTime}
defaultTimeFormat={defaultTimeFormat}
timeFormat={timeFormat}
timezone={timezone}
/>
</BottomWidgetInner>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,7 @@ function AnimationControllerFactory() {
const {children} = this.props;

return typeof children === 'function'
? children({
isAnimating,
start: this._startAnimation,
pause: this._pauseAnimation,
reset: this._resetAnimation
})
? children(isAnimating, this._startAnimation, this._pauseAnimation, this._resetAnimation)
: null;
}
}
Expand Down
101 changes: 56 additions & 45 deletions src/components/common/animation-control/floating-time-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,36 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {PureComponent} from 'react';
import React, {useMemo} from 'react';
import styled from 'styled-components';
import moment from 'moment';
import {createSelector} from 'reselect';
import {Minus} from 'components/common/icons';
import {DEFAULT_TIME_FORMAT} from 'constants/default-settings';
import {CenterFlexbox} from 'components/common/styled-components';
import {datetimeFormatter} from 'utils/data-utils';

const StyledTimeDisplay = styled.div`
const StyledTimeDisplayWrapper = styled.div.attrs({
className: 'floating-time-display'
})`
bottom: ${props => `calc(100% + ${props.theme.bottomPanelGap}px)`};
display: flex;
position: absolute;
width: 100%;
margin-left: -${props => props.theme.bottomInnerPdSide}px;
justify-content: center;
`;

const StyledTimeDisplay = styled.div.attrs({
className: 'floating-time-display__inner'
})`
background-color: ${props => props.theme.panelBackground};
border-radius: ${props => props.theme.timeDisplayBorderRadius}px;
bottom: ${props => `calc(100% + ${props.theme.bottomPanelGap}px)`};
color: ${props => props.theme.titleTextColor};
display: flex;
height: ${props => props.theme.timeDisplayHeight}px;
justify-content: center;
left: calc(50% - 88px);
min-width: ${props => props.theme.timeDisplayMinWidth}px;
opacity: ${props => props.theme.timeDisplayOpacity};
padding: ${props => props.theme.timeDisplayPadding};
position: absolute;
`;

const StyledTimeDisplayGroups = styled.div`
Expand Down Expand Up @@ -94,45 +103,52 @@ const TimeDivider = () => (

const TimeDisplayRow = ({timeValues = []}) => (
<CenterFlexbox>
<div>{timeValues[0]}</div>
<div className="time-value">{timeValues[0]}</div>
{timeValues[1] ? <TimeDivider /> : null}
{timeValues[1] ? <div>{timeValues[1]}</div> : null}
{timeValues[1] ? <div className="time-value">{timeValues[1]}</div> : null}
</CenterFlexbox>
);

export default function FloatingTimeDisplayFactory() {
class FloatingTimeDisplay extends PureComponent {
timeSelector = props => props.currentTime;
formatSelector = props => props.format;
displayTimeSelector = createSelector(
this.timeSelector,
this.formatSelector,
(currentTime, format) => {
const groupTime = Array.isArray(currentTime) ? currentTime : [currentTime];
return groupTime.reduce(
(accu, curr) => {
const displayDateTime = moment.utc(curr).format(format);
const [displayDate, displayTime] = displayDateTime.split(' ');

if (!accu.displayDate.includes(displayDate)) {
accu.displayDate.push(displayDate);
}
accu.displayTime.push(displayTime);

return accu;
},
{displayDate: [], displayTime: []}
);
const FloatingTimeDisplay = ({currentTime, defaultTimeFormat, timeFormat, timezone}) => {
const {displayDate, displayTime} = useMemo(() => {
const groupTime = Array.isArray(currentTime) ? currentTime : [currentTime];
const hasUserFormat = typeof timeFormat === 'string';
const currentFormat = (hasUserFormat ? timeFormat : defaultTimeFormat) || DEFAULT_TIME_FORMAT;
const dateFunc = datetimeFormatter(timezone);

if (hasUserFormat) {
// dont split time if user defined it
return {
displayDate: groupTime.map(dateFunc(currentFormat)),
displayTime: []
};
}
);
return groupTime.reduce(
(accu, curr) => {
const [dateFormat, datetimeFormat] = currentFormat.split(' ');
const dateString = dateFunc(dateFormat)(curr);
const timeString = datetimeFormat ? dateFunc(datetimeFormat)(curr) : null;

if (!accu.displayDate.includes(dateString)) {
accu.displayDate.push(dateString);
}
if (timeString) {
accu.displayTime.push(timeString);
}

return accu;
},
{displayDate: [], displayTime: []}
);
}, [currentTime, timeFormat, defaultTimeFormat, timezone]);

render() {
const {displayDate, displayTime} = this.displayTimeSelector(this.props);
const twoGroups = displayDate.length === 2 && displayTime.length === 2;
const bottomRow = displayTime.length ? displayTime : displayDate.length ? displayDate : null;
const topRow = displayDate.length && displayTime.length ? displayDate : null;
const twoGroups = displayDate.length === 2 && displayTime.length === 2;
const bottomRow = displayTime.length ? displayTime : displayDate.length ? displayDate : null;
const topRow = displayDate.length && displayTime.length ? displayDate : null;

return (
return (
<StyledTimeDisplayWrapper>
<StyledTimeDisplay className="animation-control__time-display">
{twoGroups ? (
<StyledTimeDisplayGroups>
Expand Down Expand Up @@ -163,13 +179,8 @@ export default function FloatingTimeDisplayFactory() {
</StyledTimeDisplayRows>
)}
</StyledTimeDisplay>
);
}
}

FloatingTimeDisplay.defaultProps = {
format: DEFAULT_TIME_FORMAT,
currentTime: null
</StyledTimeDisplayWrapper>
);
};

return FloatingTimeDisplay;
Expand Down
Loading