From 4f293e06c667f26e9d05d8d98706020ab7450b58 Mon Sep 17 00:00:00 2001 From: Mussab Abdalla Date: Fri, 30 Nov 2018 11:04:02 +0100 Subject: [PATCH] Fix #3345 fixing the initial component re-rendering (#3376) --- .../time}/TimelineComponent.jsx | 26 ++- .../time/__tests__/TimelineComponent-test.jsx | 43 +++++ web/client/plugins/Timeline.jsx | 7 +- .../timeline/InlineDateTimeSelector.jsx | 159 ------------------ web/client/plugins/timeline/Timeline.jsx | 2 +- 5 files changed, 67 insertions(+), 170 deletions(-) rename web/client/{plugins/timeline => components/time}/TimelineComponent.jsx (81%) create mode 100644 web/client/components/time/__tests__/TimelineComponent-test.jsx delete mode 100644 web/client/plugins/timeline/InlineDateTimeSelector.jsx diff --git a/web/client/plugins/timeline/TimelineComponent.jsx b/web/client/components/time/TimelineComponent.jsx similarity index 81% rename from web/client/plugins/timeline/TimelineComponent.jsx rename to web/client/components/time/TimelineComponent.jsx index 096143fe15..dcde943d92 100644 --- a/web/client/plugins/timeline/TimelineComponent.jsx +++ b/web/client/components/time/TimelineComponent.jsx @@ -83,12 +83,11 @@ class Timeline extends React.Component { shouldComponentUpdate(nextProps) { const { items, groups, options, selection, customTimes } = this.props; - const itemsChange = items !== nextProps.items || (items || []).length !== (nextProps.items || []).length; + const itemsChange = items !== nextProps.items; const groupsChange = groups !== nextProps.groups; const optionsChange = options !== nextProps.options; const customTimesChange = customTimes !== nextProps.customTimes; const selectionChange = selection !== nextProps.selection; - return ( itemsChange || groupsChange || @@ -130,8 +129,8 @@ class Timeline extends React.Component { timelineOptions = omit(options, 'start', 'end'); this.$el.setWindow(options.start, options.end, { - animation: animate - }); + animation: animate + }); } this.$el.setOptions(timelineOptions); @@ -142,7 +141,24 @@ class Timeline extends React.Component { this.$el.setGroups(groupsDataset); } - this.$el.setItems(items); + /* + ** the init() function keeps re-triggerd with change of some props + * when the item is set at the first init(), it return no range (vis-timelin.js) + * in this case we emit 'change' action to create a view range from th new items. + **/ + if ( items && items.length > 0) { + // first setItems is triggerd only when the component recieve items. trigger 'change' event to create view range. + if (!this.$el.initialFitDone) { + this.$el.setItems(items); + this.$el.emit('changed'); + }else { + // when we have a change in items we perform set item (e.g zoom /move events) + this.$el.setItems(items); + } + // when there is a view range but no items on the timeline (e.g. user moved the timeline where there are no data). we re-set items again + }else if (this.$el.initialRangeChangeDone) { + this.$el.setItems(items); + } this.$el.setSelection(selection, selectionOptions); if (currentTime) { diff --git a/web/client/components/time/__tests__/TimelineComponent-test.jsx b/web/client/components/time/__tests__/TimelineComponent-test.jsx new file mode 100644 index 0000000000..75ef8982fa --- /dev/null +++ b/web/client/components/time/__tests__/TimelineComponent-test.jsx @@ -0,0 +1,43 @@ +/* + * Copyright 2018, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const ReactDOM = require('react-dom'); +const Timeline = require('../TimelineComponent'); +const expect = require('expect'); + +describe('test TimelineComponent module component', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('test TimelineComponent creation', () => { + const comp = ReactDOM.render(, document.getElementById("container")); + expect(comp).toExist(); + + }); + it('test TimelineComponent re-rendering on props change', () => { + const actions = { + rangeChange: () => { } + }; + const comp = ReactDOM.render(, document.getElementById("container")); + const reComp = ReactDOM.render(, document.getElementById("container")); + expect(comp).toExist(); + expect(reComp).toExist(); + const point = document.querySelector('.vis-point'); + expect(point).toExist(); + }); + +}); diff --git a/web/client/plugins/Timeline.jsx b/web/client/plugins/Timeline.jsx index 551645a96b..2fb62d2095 100644 --- a/web/client/plugins/Timeline.jsx +++ b/web/client/plugins/Timeline.jsx @@ -13,7 +13,7 @@ const Timeline = require('./timeline/Timeline'); const InlineDateTimeSelector = require('../components/time/InlineDateTimeSelector'); const Toolbar = require('../components/misc/toolbar/Toolbar'); const { offsetEnabledSelector, currentTimeSelector, layersWithTimeDataSelector } = require('../selectors/dimension'); -const { selectedLayerSelector, currentTimeRangeSelector } = require('../selectors/timeline'); +const { currentTimeRangeSelector } = require('../selectors/timeline'); const { mapLayoutValuesSelector } = require('../selectors/maplayout'); const { withState, compose, branch, renderNothing, withStateHandlers, withProps, defaultProps } = require('recompose'); @@ -41,15 +41,13 @@ const TimelinePlugin = compose( connect( createSelector( layersWithTimeDataSelector, - selectedLayerSelector, currentTimeSelector, currentTimeRangeSelector, offsetEnabledSelector, playbackRangeSelector, statusSelector, - (layers, selectedLayer, currentTime, currentTimeRange, offsetEnabled, playbackRange, status) => ({ + (layers, currentTime, currentTimeRange, offsetEnabled, playbackRange, status) => ({ layers, - selectedLayer, currentTime, currentTimeRange, offsetEnabled, @@ -236,7 +234,6 @@ const TimelinePlugin = compose( visible: canExpand, tooltip: , glyph: collapsed ? 'resize-full' : 'resize-small', - // we perform a check if the timeline data is loaded before allowing the user to expand the timeline and render an empty component onClick: () => setOptions({ ...options, collapsed: !collapsed }) } ]} /> diff --git a/web/client/plugins/timeline/InlineDateTimeSelector.jsx b/web/client/plugins/timeline/InlineDateTimeSelector.jsx deleted file mode 100644 index 5490df015d..0000000000 --- a/web/client/plugins/timeline/InlineDateTimeSelector.jsx +++ /dev/null @@ -1,159 +0,0 @@ - -/* - * Copyright 2018, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -const React = require('react'); -const PropTypes = require('prop-types'); -const {Form, FormGroup, FormControl, Glyphicon: GlyphiconRB, Button} = require('react-bootstrap'); -const tooltip = require('../../components/misc/enhancers/tooltip'); -const Glyphicon = tooltip(GlyphiconRB); -const {padStart, isNil} = require('lodash'); -const moment = require('moment'); - -class InlineDateTimeSelector extends React.Component { - static propTypes = { - date: PropTypes.string, - onUpdate: PropTypes.func, - glyph: PropTypes.string, - style: PropTypes.object, - className: PropTypes.string, - tooltip: PropTypes.string - }; - - static defaultProps = { - date: '', - onUpdate: () => {}, - glyph: 'time', - style: {}, - className: '', - tooltip: '' - }; - - onUpdate = (key, add) => { - const currentTime = moment(this.props.date).utc(); - const newTime = add ? moment(currentTime).add(1, key) : moment(currentTime).subtract(1, key); - this.props.onUpdate(newTime.toISOString()); - }; - - onChange = (key, value, parseValue = val => val) => { - const currentTime = moment(this.props.date).utc(); - const newTime = currentTime[key] && currentTime[key](parseValue(value)); - this.props.onUpdate(newTime.toISOString()); - }; - - getForm = () => { - - const currentTime = this.props.date && moment(this.props.date).utc(); - - return [ - { - name: 'icon', - value: 'calendar', - type: 'icon' - }, - { - name: 'day', - value: currentTime && currentTime.date() - }, - { - name: 'month', - value: currentTime && currentTime.month(), - format: value => !isNil(value) && moment.monthsShort(value), - parseValue: value => value - 1 - }, - { - name: 'year', - value: currentTime && currentTime.year() - }, - { - name: 'icon', - value: 'time', - type: 'icon' - }, - { - name: 'hours', - value: currentTime && currentTime.hours() - }, - { - name: 'separator', - value: ':', - type: 'separator' - }, - { - name: 'minutes', - value: currentTime && currentTime.minutes() - }, - { - name: 'separator', - value: ':', - type: 'separator' - }, - { - name: 'seconds', - value: currentTime && currentTime.seconds() - }, - { - name: 'separator', - value: currentTime && currentTime.utcOffset(), - type: 'separator', - format: value => 'UTC ' + (value >= 0 ? '+' : '-') + padStart(value / 60, 2, 0) - } - ]; - }; - - render() { - const formStructure = this.getForm(); - return ( -
- - {this.props.glyph && } - {formStructure.map(el => - el.type === 'icon' && -
- -
- || - el.type === 'separator' && -
- {el.format && el.format(el.value) || el.value} -
- ||
- - this.onChange(el.name, event.target.value, el.parseValue)}/> - -
- - )} -
-
- ); - } -} - -module.exports = InlineDateTimeSelector; diff --git a/web/client/plugins/timeline/Timeline.jsx b/web/client/plugins/timeline/Timeline.jsx index 796e2832ed..7d2610f190 100644 --- a/web/client/plugins/timeline/Timeline.jsx +++ b/web/client/plugins/timeline/Timeline.jsx @@ -327,6 +327,6 @@ const enhance = compose( }) ) ); -const Timeline = require('./TimelineComponent'); +const Timeline = require('../../components/time/TimelineComponent'); module.exports = enhance(Timeline);