diff --git a/source/core/datasource/DataSource.datasource.js b/source/core/datasource/DataSource.datasource.js index 16c4463e90..ec8cab1e6b 100644 --- a/source/core/datasource/DataSource.datasource.js +++ b/source/core/datasource/DataSource.datasource.js @@ -208,6 +208,7 @@ class DataSource { async onDisconnect(){} reset() {} + } export default DataSource; diff --git a/source/core/datasource/TimeSeries.datasource.js b/source/core/datasource/TimeSeries.datasource.js index 6873343045..6e9214f08e 100644 --- a/source/core/datasource/TimeSeries.datasource.js +++ b/source/core/datasource/TimeSeries.datasource.js @@ -167,12 +167,17 @@ class TimeSeriesDatasource extends DataSource { async initDataSource(properties) { await super.initDataSource(properties); return new Promise(async (resolve, reject) => { + const topics = { + data: this.getTopicId(), + time: this.getTimeTopicId() + }; + if(this.dataSynchronizer) { + topics.sync = dataSynchronizer.getTimeTopicId() + } + this.postMessage({ message: 'topics', - topics: { - data: this.getTopicId(), - time: this.getTimeTopicId() - }, + topics: topics, }, async () => { // listen for Events to callback to subscriptions const datasourceBroadcastChannel = new BroadcastChannel(this.getTimeTopicId()); @@ -207,7 +212,7 @@ class TimeSeriesDatasource extends DataSource { reconnect= false, mode= this.getMode()) { - + await this.checkInit(); let intersectStartTime = startTime; let intersectEndTime = endTime; @@ -227,13 +232,15 @@ class TimeSeriesDatasource extends DataSource { intersectEndTime = (endDelta < 0) ? this.getMaxTime() : endTime; } - return this.updateProperties({ - startTime: intersectStartTime, - endTime: intersectEndTime, - replaySpeed: replaySpeed, - reconnect : reconnect, - mode: mode - }); + if(intersectEndTime !== this.getMinTime() || intersectEndTime !== this.getMaxTime()) { + return this.updateProperties({ + startTime: intersectStartTime, + endTime: intersectEndTime, + replaySpeed: replaySpeed, + reconnect: reconnect, + mode: mode + }); + } } } diff --git a/source/core/datasource/common/handler/TimeSeries.handler.js b/source/core/datasource/common/handler/TimeSeries.handler.js index ae2a0d0ffb..bb194a25ff 100644 --- a/source/core/datasource/common/handler/TimeSeries.handler.js +++ b/source/core/datasource/common/handler/TimeSeries.handler.js @@ -165,7 +165,6 @@ class DelegateReplayHandler extends DelegateHandler { this.timeBc = new BroadcastChannel(this.timeTopic); this.timeBc.onmessage = async (event) => { if (event.data.type === EventType.MASTER_TIME) { - masterTimestamp = event.data.timestamp; if (masterTimestamp >= endTimestamp) { await this.disconnect(); @@ -178,10 +177,12 @@ class DelegateReplayHandler extends DelegateHandler { // less than 5 sec if (dTimestamp <= prefetchBatchDuration) { // request next batch - data = await this.context.nextBatch(); - if (!this.status.cancel && data.length > 0) { - this.handleData(data); - lastTimestamp = data[data.length - 1].timestamp; + if (!this.status.cancel) { + data = await this.context.nextBatch(); + if (data.length > 0) { + this.handleData(data); + lastTimestamp = data[data.length - 1].timestamp; + } } } fetching = false; @@ -229,6 +230,7 @@ class DelegateReplayHandler extends DelegateHandler { this.context.disconnect(); if (isDefined(this.timeBc)) { this.timeBc.close(); + this.timeBc = undefined; } this.initialized = false; } catch (ex) { diff --git a/source/core/datasource/sweapi/context/SweApi.replay.context.js b/source/core/datasource/sweapi/context/SweApi.replay.context.js index e2c2d4dfee..69afafec7e 100644 --- a/source/core/datasource/sweapi/context/SweApi.replay.context.js +++ b/source/core/datasource/sweapi/context/SweApi.replay.context.js @@ -98,7 +98,10 @@ class SweApiReplayContext extends SweApiContext { console.warn(`fetching ${relativeStartTime} -> ` + `${this.properties.endTime} for datasource ${this.properties.dataSourceId}`); - this.collection = await this.replayFunction(properties, relativeStartTime, this.properties.endTime); + // if disconnected, replay function is reset + if(this.replayFunction) { + this.collection = await this.replayFunction(properties, relativeStartTime, this.properties.endTime); + } } const fetchNext = async () => { diff --git a/source/core/timesync/DataSynchronizer.js b/source/core/timesync/DataSynchronizer.js index 5eae72d468..5ed1a7b3f5 100644 --- a/source/core/timesync/DataSynchronizer.js +++ b/source/core/timesync/DataSynchronizer.js @@ -39,8 +39,8 @@ class DataSynchronizer { this.dataSources = properties.dataSources || []; this.replaySpeed = properties.replaySpeed || 1; this.timerResolution = properties.timerResolution || 5; - this.masterTimeRefreshRate = properties.masterTimeRefreshRate || 250, - this.mode = properties.mode || Mode.REPLAY; + this.masterTimeRefreshRate = properties.masterTimeRefreshRate || 250; + this.mode = properties.mode || Mode.REPLAY; this.initialized = false; this.properties = {}; this.properties.replaySpeed = this.replaySpeed; @@ -63,7 +63,6 @@ class DataSynchronizer { this.properties.startTime = 'now'; this.properties.endTime = '2055-01-01Z'; } - } getTopicId() { @@ -287,44 +286,81 @@ class DataSynchronizer { * Adds a new DataSource object to the list of datasources to synchronize. * note: don't forget to call reset() to be sure to re-init the synchronizer internal properties. * @param {Datasource} dataSource - the new datasource to add - * @param [lazy=false] lazy - add to current running synchronizer */ - async addDataSource(dataSource, lazy = false) { - dataSource.checkInit(); - if(lazy) { - return new Promise(async resolve => { + async addDataSource(dataSource) { + return new Promise(async resolve => { + if (!this.initialized) { + console.log(`DataSynchronizer not initialized yet, add DataSource ${dataSource.id} as it`); + this.dataSources.push(dataSource); + this.onTimeChanged(this.getMinTime(), this.getMaxTime()); + } else { const dataSourceForWorker = await this.createDataSourceForWorker(dataSource); + const lastTimestamp = (await this.getCurrentTime()).data; + if (isDefined(lastTimestamp)) { + const minDsTimestamp = new Date(dataSource.getMinTime()).getTime(); + const maxDsTimestamp = new Date(dataSource.getMaxTime()).getTime(); + const current = lastTimestamp + 1000; + if(current > minDsTimestamp && current < maxDsTimestamp) { + await dataSource.setTimeRange( + new Date(lastTimestamp + 1000).toISOString(), + ); + } + } this.dataSources.push(dataSource); await this.postMessage({ message: 'add', dataSources: [dataSourceForWorker] + }, async () => { + if (this.dataSources.length === 1) { + await this.postMessage({ + message: 'update-properties', + mode: this.mode, + replaySpeed: this.replaySpeed, + startTime: this.getMinTime(), + endTime: this.getMaxTime() + }, () => { + this.onTimeChanged(this.getMinTime(), this.getMaxTime()); + this.onAddedDataSource(dataSource.id); + resolve(); + }); + } else { + this.onTimeChanged(this.getMinTime(), this.getMaxTime()); + this.onAddedDataSource(dataSource.id); + resolve(); + } }); - await dataSource.connect(); - resolve(); - }); - } else { - this.dataSources.push(dataSource); - } + } + }); } /** * Removes a DataSource object from the list of datasources of the synchronizer. * @param {DataSource} dataSource - the new datasource to add - * @param [lazy=false] lazy - remove from the current running synchronizer */ - async removeDataSource(dataSource, lazy = false) { - if(lazy) { - return new Promise(async resolve => { - this.dataSources = this.dataSources.filter( elt => elt.id !== dataSource.getId()); - await this.postMessage({ - message: 'remove', - dataSources: [dataSource.getId()] - }); - await dataSource.disconnect(); - resolve(); - }); - } else { + async removeDataSource(dataSource) { + if(!this.initialized) { this.dataSources = this.dataSources.filter( elt => elt.id !== dataSource.getId()); + this.onTimeChanged(this.getMinTime(),this.getMaxTime()); + } else { + return new Promise(async (resolve, reject) => { + try { + this.dataSources = this.dataSources.filter(elt => elt.id !== dataSource.getId()); + await this.postMessage({ + message: 'remove', + dataSourceIds: [dataSource.getId()] + }); + await dataSource.disconnect(); + if(this.dataSources.length > 0) { + this.onTimeChanged(this.getMinTime(), this.getMaxTime()); + } else { + await this.reset(); + } + this.onRemovedDataSource(dataSource.id); + resolve(); + }catch (ex) { + reject(ex); + } + }); } } @@ -348,8 +384,12 @@ class DataSynchronizer { * Connects all dataSources */ async connect() { - await this.checkInit(); - await this.doConnect(); + if((this.mode === Mode.REPLAY && this.dataSources.length === 0)) { + return; + } else { + await this.checkInit(); + await this.doConnect(); + } } async checkInit() { @@ -444,8 +484,11 @@ class DataSynchronizer { startTime: startTime, endTime: endTime }, () => { - for (let ds of this.dataSources) { - ds.setTimeRange(startTime, endTime, replaySpeed, reconnect, mode); + if(this.dataSources.length > 0 ) { + for (let ds of this.dataSources) { + ds.setTimeRange(startTime, endTime, replaySpeed, reconnect, mode); + } + this.onTimeChanged(this.getMinTime(),this.getMaxTime()); } this.mode = mode; resolve(); @@ -458,6 +501,18 @@ class DataSynchronizer { ds.updateProperties(properties); } } + + resetTimes() { + this.lastTime = { + start: undefined, + end: undefined + }; + this.startTime = 0; + this.minTime = 0; + this.maxTime = 0; + this.endTime = 0; + this.lastTime = 0; + } /** * Resets reference time */ @@ -466,7 +521,9 @@ class DataSynchronizer { await this.checkInit(); await this.postMessage({ message: 'reset' - }, resolve); + }); + this.resetTimes(); + resolve(); }); } @@ -485,12 +542,16 @@ class DataSynchronizer { * Connect the dataSource then the protocol will be opened as well. */ async isConnected() { - for (let ds of this.dataSources) { - if (!(await ds.isConnected())) { - return false; - } + if(this.dataSources.length === 0) { + return false; + } else { + await this.checkInit(); + return new Promise(async resolve => { + await this.postMessage({ + message: 'is-connected' + }, (message) => resolve(message.data)); + }); } - return true; } async postMessage(props, Fn, checkInit = true) { @@ -516,5 +577,10 @@ class DataSynchronizer { } } } + onTimeChanged(start, min){} + + onRemovedDataSource(dataSourceId){} + + onAddedDataSource(dataSourceId){} } export default DataSynchronizer; diff --git a/source/core/timesync/DataSynchronizer.worker.js b/source/core/timesync/DataSynchronizer.worker.js index 19010665b2..25fdb8f0d6 100644 --- a/source/core/timesync/DataSynchronizer.worker.js +++ b/source/core/timesync/DataSynchronizer.worker.js @@ -73,9 +73,15 @@ async function handleMessage(event) { } else if (event.data.message === 'connect') { startMasterTimeInterval(masterTimeRefreshRate); dataSynchronizerAlgo.checkStart(); - } else if (event.data.message === 'remove' && event.data.dataSources) { + } else if(event.data.message === 'is-connected') { + console.log(isDefined(masterTimeInterval), isDefined(dataSynchronizerAlgo), isDefined(dataSynchronizerAlgo.interval)) + data = { + message: 'is-connected', + data: isDefined(masterTimeInterval) && isDefined(dataSynchronizerAlgo) && isDefined(dataSynchronizerAlgo.interval) + }; + } else if (event.data.message === 'remove' && event.data.dataSourceIds) { console.log('Remove datasource from synchronizer..') - await removeDataSources(event.data.dataSources); + await removeDataSources(event.data.dataSourceIds); } else if (event.data.message === 'current-time') { data = { message: 'current-time', @@ -160,9 +166,11 @@ function initBroadcastChannel(dataTopic, timeTopic) { } else if(event.data.type === EventType.STATUS) { const dataSourceId = event.data.dataSourceId; dataSynchronizerAlgo.setStatus(dataSourceId, event.data.status); - console.log(dataSources[dataSourceId].name + ": status=" + event.data.status); // bubble the message - bcChannels[dataSourceId].postMessage(event.data); + if(dataSourceId in bcChannels) { + console.log(dataSources[dataSourceId].name + ": status=" + event.data.status); + bcChannels[dataSourceId].postMessage(event.data); + } } } @@ -192,19 +200,20 @@ function addDataSource(dataSource) { /** * - * @param dataSources + * @param dataSourceIds */ -async function removeDataSources(dataSources) { - for(let dataSource of dataSources) { - await removeDataSource(dataSource); +async function removeDataSources(dataSourceIds) { + for(let dataSourceId of dataSourceIds) { + await removeDataSource(dataSourceId); } } -async function removeDataSource(dataSource) { - await dataSynchronizerAlgo.removeDataSource(dataSource); +async function removeDataSource(dataSourceId) { + await dataSynchronizerAlgo.removeDataSource(dataSourceId); // create a BC to push back the synchronized data into the DATA Stream. - delete bcChannels[dataSource.id]; - delete dataSources[dataSource.id]; + console.log('deleting BC for datasource '+dataSourceId); + delete bcChannels[dataSourceId]; + delete dataSources[dataSourceId]; } function checkMasterTime() { diff --git a/source/core/timesync/DataSynchronizerAlgo.realtime.js b/source/core/timesync/DataSynchronizerAlgo.realtime.js index 7bd9b37285..28bb677186 100644 --- a/source/core/timesync/DataSynchronizerAlgo.realtime.js +++ b/source/core/timesync/DataSynchronizerAlgo.realtime.js @@ -48,10 +48,10 @@ class DataSynchronizerAlgoRealtime extends DataSynchronizerAlgo { } } const dClock = (performance.now() - refClockTime); - this.tsRun = tsRef + dClock; // compute next data to return for (let currentDsId in this.dataSourceMap) { currentDs = this.dataSourceMap[currentDsId]; + this.tsRun = tsRef + dClock; if (currentDs.dataBuffer.length > 0) { const dTs = (currentDs.dataBuffer[0].data.timestamp - tsRef); const dClockAdj = dClock - maxLatency; diff --git a/source/core/timesync/DataSynchronizerAlgo.replay.js b/source/core/timesync/DataSynchronizerAlgo.replay.js index dcb463d34d..dc73042a18 100644 --- a/source/core/timesync/DataSynchronizerAlgo.replay.js +++ b/source/core/timesync/DataSynchronizerAlgo.replay.js @@ -15,13 +15,15 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { return; } - const ds = this.dataSourceMap[dataSourceId]; - const lastData = dataBlocks[dataBlocks.length-1]; - if (!this.checkVersion(ds, lastData)) { - console.warn(`[DataSynchronizer] incompatible version ${ds.version} ~ ${lastData.version}, skipping data`); - return; + if(dataSourceId in this.dataSourceMap) { + const ds = this.dataSourceMap[dataSourceId]; + const lastData = dataBlocks[dataBlocks.length - 1]; + if (!this.checkVersion(ds, lastData)) { + console.warn(`[DataSynchronizer] incompatible version ${ds.version} ~ ${lastData.version}, skipping data`); + return; + } + ds.dataBuffer.push(...dataBlocks); } - ds.dataBuffer.push(...dataBlocks); } processData() { @@ -29,8 +31,12 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { this.interval = setInterval(() => { // 1) return the oldest data if any - while (this.computeNextData(this.startTimestamp, clockTimeRef)) {} - this.checkEnd(); + while (this.computeNextData(this.startTimestamp, clockTimeRef)) { + this.checkEnd(); + if(!isDefined(this.interval)) { + break; + } + } }, this.timerResolution); console.warn(`Started Replay Algorithm with tsRef=${new Date(this.startTimestamp).toISOString()}`); } @@ -46,10 +52,17 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { let currentDsToShift = null; const dClock = (performance.now() - refClockTime) * this.replaySpeed; - this.tsRun = tsRef + dClock; + let tsRun = tsRef + dClock; // compute next data to return for (let currentDsId in this.dataSourceMap) { currentDs = this.dataSourceMap[currentDsId]; + if(currentDs.skip) { + // if datasource is in current range + if(tsRun > currentDs.minTimestamp && tsRun < currentDs.maxTimestamp) { + currentDs.skip = false; + } + } + this.tsRun = tsRun; // skip DatSource if out of time range if(currentDs.skip) continue; @@ -72,7 +85,9 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { // finally pop the data from DS queue if (currentDsToShift !== null) { - this.onData(currentDsToShift.id, currentDsToShift.dataBuffer.shift()); + if(currentDsToShift.id in this.dataSourceMap) { + this.onData(currentDsToShift.id, currentDsToShift.dataBuffer.shift()); + } return true; } return false; @@ -89,18 +104,29 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { name: dataSource.name || dataSource.id, status: Status.DISCONNECTED, //MEANING Enabled, 0 = Disabled version: undefined, - minTime: dataSource.minTimestamp, - maxTime: dataSource.maxTimestamp, + minTimestamp: dataSource.minTimestamp, + maxTimestamp: dataSource.maxTimestamp, skip: false }; + if(dataSource.maxTimestamp < this.getCurrentTimestamp() || dataSource.minTimestamp > this.getCurrentTimestamp()) { + this.dataSourceMap[dataSource.id].skip = true; + console.warn(`Skipping new added dataSource ${dataSource.id} because timeRange of the dataSource is not intersecting the synchronizer one`); + } this.datasources.push(dataSource); + + // check if start at the right startTime + if(this.dataSourceMap.length === 1) { + // reset min/max to that dataSource + } } checkVersion(datasource, dataBlock) { - if(!isDefined(datasource.version)) { - return true; - } else if(datasource.version !== dataBlock.version) { - return false; + if(isDefined(datasource)) { + if (!isDefined(datasource.version)) { + return true; + } else if (datasource.version !== dataBlock.version) { + return false; + } } } @@ -123,10 +149,13 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { let nbFetch = 0 let totalDataSources = Object.keys(this.dataSourceMap).length; + if(totalDataSources === 0) { + return; + } let dataSource; for(let dataSourceID in this.dataSourceMap) { dataSource = this.dataSourceMap[dataSourceID]; - dataSource.skip = (this.startTimestamp < dataSource.minTime) || (this.startTimestamp > dataSource.maxTime); + dataSource.skip = (this.startTimestamp < dataSource.minTimestamp) || (this.startTimestamp > dataSource.maxTimestamp); if(dataSource.status === Status.FETCH_STARTED){ nbFetch++; } else if(dataSource.skip) { @@ -152,12 +181,12 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { } reset() { - this.tsRun = undefined; console.log('reset synchronizer algo') this.close(); for (let currentDsId in this.dataSourceMap) { this.resetDataSource(currentDsId); } + this.tsRun = undefined; } resetDataSource(datasourceId) { @@ -168,6 +197,35 @@ class DataSynchronizerAlgoReplay extends DataSynchronizerAlgo { currentDs.skip = false; } + removeDataSource(dataSourceId) { + super.removeDataSource(dataSourceId); + // looking for next start Timestamp + let currentTimestamp = this.getCurrentTimestamp(); + let min, ds; + for(let dsKey in this.dataSourceMap) { + ds = this.dataSourceMap[dsKey]; + if(currentTimestamp >= ds.minTimestamp && currentTimestamp <= ds.maxTimestamp) { + // continue because this datasource is in the current range + return; + } else { + // otherwise + // looking for next range and reset algo + if(!min) { + min = ds.minTimestamp; + } else if(ds.minTimestamp < min) { + min = ds.minTimestamp; + } + } + } + + if(min) { + this.reset(); + this.startTimestamp = min; + console.log('set this.startTimestamp to ' + new Date(this.startTimestamp).toISOString()); + this.checkStart(); + } + } + onEnd() {} onStart() {} } diff --git a/source/ext/ui/view/rangeslider/RangeSliderView.replay.js b/source/ext/ui/view/rangeslider/RangeSliderView.replay.js index 90f39f38f9..bd7046c352 100644 --- a/source/ext/ui/view/rangeslider/RangeSliderView.replay.js +++ b/source/ext/ui/view/rangeslider/RangeSliderView.replay.js @@ -34,203 +34,248 @@ import * as wNumb from 'wnumb'; }); */ class RangeSliderViewReplay extends View { - /** - * Create the discoveryView - * @param {Object} [properties={}] - the properties of the view - * @param {String} properties.container - The div element to attach to - * @param {Object[]} [properties.layers=[]] - The initial layers to add - * @param {Number} properties.startTime - The start time - * @param {Number} properties.endTime - The end time - * @param {String} properties.dataSource - The dataSourceObject - * @param {Number} [properties.debounce=0] - Debounce time after updating the slider - * @param {Boolean} properties.disabled - disabled the range slider - * @param {Object} properties.dataSynchronizer - a data synchronizer to get current data time for this set of datasources - */ - constructor(properties) { - super({ - ...properties, - supportedLayers: ['data'] - }); - - this.slider = document.createElement("div"); - this.slider.setAttribute("class", "core-rangeslider-slider"); - document.getElementById(this.divId).appendChild(this.slider); - - let startTime = new Date().getTime(); - this.endTime = new Date("2055-01-01T00:00:00Z").getTime(); //01/01/2055 - - this.update = false; - this.dataSourceObject = null; - this.debounce = 0; - this.options = {}; - this.sliding = false; - - if (isDefined(properties)) { - if (isDefined(properties.startTime)) { - startTime = new Date(properties.startTime).getTime(); - } - - if (isDefined(properties.endTime)) { - this.endTime = new Date(properties.endTime).getTime(); - } - - if (isDefined(properties.dataSynchronizer)) { - this.dataSourceObject = properties.dataSynchronizer; - } - - if (isDefined(properties.dataSource)) { - this.dataSourceObject = properties.dataSource; - } - - if (isDefined(properties.debounce)) { - this.debounce = parseInt(properties.debounce); - } - - if(isDefined(properties.options)) { - this.options = properties.options; - } - - if(isDefined(properties.disabled)) { - this.slider.setAttribute('disabled', properties.disabled); - } - } + /** + * Create the discoveryView + * @param {Object} [properties={}] - the properties of the view + * @param {String} properties.container - The div element to attach to + * @param {Object[]} [properties.layers=[]] - The initial layers to add + * @param {Number} properties.startTime - The start time (lower handle) as ISO date + * @param {Number} properties.endTime - The end time (upper handle) as ISO date + * @param {Number} properties.minTimeRange - The min range time as ISO date + * @param {Number} properties.maxTimeRange - The max range as ISO date + * @param {String} properties.dataSource - The dataSourceObject + * @param {Number} [properties.debounce=0] - Debounce time after updating the slider + * @param {Boolean} properties.disabled - disabled the range slider + * @param {Object} properties.dataSynchronizer - a data synchronizer to get current data time for this set of datasources + */ + constructor(properties) { + super({ + ...properties, + supportedLayers: ['data'] + }); + + this.slider = document.createElement("div"); + this.slider.setAttribute("class", "core-rangeslider-slider"); + document.getElementById(this.divId).appendChild(this.slider); + + let startTimestamp = new Date().getTime(); + let endTimestamp = new Date("2055-01-01T00:00:00Z").getTime(); //01/01/2055 + + let minTimeRangeTimestamp = startTimestamp; + let maxTimeRangeTimestamp = endTimestamp; + + this.update = false; + this.dataSourceObject = null; + this.debounce = 0; + this.options = {}; + this.sliding = false; + + if (isDefined(properties)) { + if (isDefined(properties.startTime)) { + startTimestamp = new Date(properties.startTime).getTime(); + } + + if (isDefined(properties.endTime)) { + endTimestamp = new Date(properties.endTime).getTime(); + } + + if (isDefined(properties.minTimeRange)) { + minTimeRangeTimestamp = new Date(properties.minTimeRange).getTime(); + } + + if (isDefined(properties.maxTimeRange)) { + maxTimeRangeTimestamp = new Date(properties.maxTimeRange).getTime(); + } + + if (isDefined(properties.dataSynchronizer)) { + this.dataSourceObject = properties.dataSynchronizer; + } + + if (isDefined(properties.dataSource)) { + this.dataSourceObject = properties.dataSource; + } + + if (isDefined(properties.debounce)) { + this.debounce = parseInt(properties.debounce); + } + + if (isDefined(properties.options)) { + this.options = properties.options; + } + + if (isDefined(properties.disabled)) { + this.slider.setAttribute('disabled', properties.disabled); + } + } - noUiSlider.create(this.slider, { - start: [startTime, this.endTime]/*,timestamp("2015-02-16T08:09:00Z")]*/, - range: { - min: startTime, - max: this.endTime - }, - //step: 1000* 60* 60, - format: wNumb({ - decimals: 0 - }), - behaviour: 'drag', - connect: true, - animate: false, - pips: { - mode: 'positions', - values: [5, 25, 50, 75], - density: 1, - //stepped: true, - format: wNumb({ - edit: function (value) { - return new Date(parseInt(value)).toISOString().replace(".000Z", "Z") - .split("T")[1].split("Z")[0].split(".")[0]; - } - }) - }, - ...this.options - }); - - this.createEvents(); - - if(isDefined(this.dataSourceObject)) { - // listen for BC - const bc = new BroadcastChannel(this.dataSourceObject.getTimeTopicId()); - bc.onmessage = (message) => { - if (!this.update) { - this.slider.noUiSlider.set([message.data.timestamp]); - this.onChange(message.data.timestamp, parseInt(this.slider.noUiSlider.get()[1]), 'data'); + const options = { + start: [startTimestamp, endTimestamp]/*,timestamp("2015-02-16T08:09:00Z")]*/, + range: { + min: minTimeRangeTimestamp, + max: maxTimeRangeTimestamp + }, + //step: 1000* 60* 60, + format: wNumb({ + decimals: 0 + }), + behaviour: 'drag', + connect: true, + animate: false, + pips: { + mode: 'positions', + values: [5, 25, 50, 75], + density: 1, + //stepped: true, + format: wNumb({ + edit: function (value) { + return new Date(parseInt(value)).toISOString().replace(".000Z", "Z") + .split("T")[1].split("Z")[0].split(".")[0]; + } + }) + }, + ...this.options + }; + + // for above listeners + this.maxTimeRangeTimestamp = maxTimeRangeTimestamp; + + noUiSlider.create(this.slider, options); + + this.createEvents(); + + if (isDefined(this.dataSourceObject)) { + // listen for BC + const bc = new BroadcastChannel(this.dataSourceObject.getTimeTopicId()); + bc.onmessage = (message) => { + if (!this.update) { + this.slider.noUiSlider.set([message.data.timestamp]); + this.onChange(message.data.timestamp, parseInt(this.slider.noUiSlider.get()[1]), 'data'); + } + } + this.bc = bc; } - } } - } - - createActivateButton() { - let activateButtonDiv = document.createElement("div"); - let aTagActivateButton = document.createElement("a"); - activateButtonDiv.appendChild(aTagActivateButton); - activateButtonDiv.setAttribute("class", "core-rangeslider-control"); - let self = this; + createActivateButton() { + let activateButtonDiv = document.createElement("div"); + let aTagActivateButton = document.createElement("a"); + activateButtonDiv.appendChild(aTagActivateButton); - activateButtonDiv.addEventListener("click", function (event) { - if (activateButtonDiv.className.indexOf("core-rangeslider-control-select") > -1) { activateButtonDiv.setAttribute("class", "core-rangeslider-control"); - self.deactivate(); - } else { - activateButtonDiv.setAttribute("class", "core-rangeslider-control-select"); - self.activate(); - } - }); - document.getElementById(this.divId).appendChild(activateButtonDiv); - } - - createEvents() { - const that = this; - //noUi-handle noUi-handle-lower - // start->update->end - this.slider.noUiSlider.on("start", function (values, handle) { - that.update = true; - that.sliding = true; - const st = parseInt(values[0]); - const end = parseInt(values[1]) || parseInt(that.endTime); - that.onChange(st, end, 'start'); - }); - - this.slider.noUiSlider.on("slide", function (values, handle) { - that.sliding = true; - that.update = true; - const st = parseInt(values[0]); - const end = parseInt(values[1]) || parseInt(that.endTime); - that.onChange(st, end, 'slide'); - }); - - this.slider.noUiSlider.on("end", function (values, handle) { - if(that.sliding) { - that.sliding = false; - const st = parseInt(values[0]); - const end = parseInt(values[1]) || parseInt(that.endTime); - that.onChange(st, end, 'end'); - // that.update = false; - setTimeout(() => that.update = false, that.debounce); - } - }); - } - - /** - * Deactivate the timeline bar - */ - deactivate() { - this.slider.setAttribute('disabled', true); - } - - /** - * Activate the timeline nar - */ - activate() { - this.slider.removeAttribute('disabled'); - } - - setData(dataSourceId, data) { - const values = data.values; - for(let i=0; i < values.length;i++) { - if(!this.update) { - this.slider.noUiSlider.set([values[i].timestamp]); - } + let self = this; + + activateButtonDiv.addEventListener("click", function (event) { + if (activateButtonDiv.className.indexOf("core-rangeslider-control-select") > -1) { + activateButtonDiv.setAttribute("class", "core-rangeslider-control"); + self.deactivate(); + } else { + activateButtonDiv.setAttribute("class", "core-rangeslider-control-select"); + self.activate(); + } + }); + document.getElementById(this.divId).appendChild(activateButtonDiv); + } + + createEvents() { + const that = this; + //noUi-handle noUi-handle-lower + // start->update->end + this.slider.noUiSlider.on("start", function (values, handle) { + that.update = true; + that.sliding = true; + const st = parseInt(values[0]); + const end = parseInt(values[1]) || parseInt(that.maxTimeRangeTimestamp); + that.onChange(st, end, 'start'); + }); + + this.slider.noUiSlider.on("slide", function (values, handle) { + that.sliding = true; + that.update = true; + const st = parseInt(values[0]); + const end = parseInt(values[1]) || parseInt(that.maxTimeRangeTimestamp); + that.onChange(st, end, 'slide'); + }); + + this.slider.noUiSlider.on("end", function (values, handle) { + if (that.sliding) { + that.sliding = false; + const st = parseInt(values[0]); + const end = parseInt(values[1]) || parseInt(that.maxTimeRangeTimestamp); + that.onChange(st, end, 'end'); + // that.update = false; + setTimeout(() => that.update = false, that.debounce); + } + }); + } + + /** + * Deactivate the timeline bar + */ + deactivate() { + this.slider.setAttribute('disabled', true); } - } - setStartTime(timestamp) { - if(!this.update) { - this.slider.noUiSlider.set([timestamp]); + /** + * Activate the timeline nar + */ + activate() { + this.slider.removeAttribute('disabled'); } - } - setTime(startTimestamp, endTimestamp) { - if(!this.update) { - this.slider.noUiSlider.set([startTimestamp, endTimestamp]); + setData(dataSourceId, data) { + const values = data.values; + for (let i = 0; i < values.length; i++) { + if (!this.update) { + this.slider.noUiSlider.set([values[i].timestamp]); + } + } } - } - onChange(startTime, endTime, type) { - if(type === 'end') { - this.dataSourceObject.setTimeRange(new Date(startTime).toISOString(), - new Date(endTime).toISOString(), this.dataSourceObject.properties.replaySpeed, true); + setStartTime(timestamp) { + if (!this.update) { + this.slider.noUiSlider.set([timestamp]); + } } - } + setTime(minRangeTimestamp, maxRangeTimestamp) { + if (!this.update) { + this.slider.noUiSlider.updateOptions({ + range: { + min: minRangeTimestamp, + max: maxRangeTimestamp + } + }); + this.slider.noUiSlider.set([null, maxRangeTimestamp]); + } + } + + onChange(startTime, endTime, type) { + if (type === 'end') { + this.dataSourceObject.setTimeRange(new Date(startTime).toISOString(), + new Date(endTime).toISOString(), this.dataSourceObject.properties.replaySpeed, true); + } + } + + destroy() { + if(isDefined(this.slider) && isDefined(this.slider.noUiSlider)) { + this.slider.noUiSlider.destroy(); + } + if (isDefined(this.bc)) { + this.bc.close(); + } + } + + disable() { + if(isDefined(this.slider)) { + this.slider.setAttribute('disabled', true); + } + } + + enable() { + if(isDefined(this.slider)) { + this.slider.removeAttribute('disabled'); + } + } } export default RangeSliderViewReplay; diff --git a/source/vue/components/ControlTime.replay.vue b/source/vue/components/ControlTime.replay.vue new file mode 100644 index 0000000000..06113a764a --- /dev/null +++ b/source/vue/components/ControlTime.replay.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/source/vue/components/RangeSlider.realtime.vue b/source/vue/components/RangeSlider.realtime.vue new file mode 100644 index 0000000000..46053029c4 --- /dev/null +++ b/source/vue/components/RangeSlider.realtime.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/source/vue/components/RangeSlider.replay.vue b/source/vue/components/RangeSlider.replay.vue new file mode 100644 index 0000000000..2247cb9c1e --- /dev/null +++ b/source/vue/components/RangeSlider.replay.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/source/vue/components/RangeSlider.vue b/source/vue/components/RangeSlider.vue new file mode 100644 index 0000000000..d2192c8916 --- /dev/null +++ b/source/vue/components/RangeSlider.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/source/vue/components/TimeController.batch.vue b/source/vue/components/TimeController.batch.vue index 97d154292f..bf46dd06f5 100644 --- a/source/vue/components/TimeController.batch.vue +++ b/source/vue/components/TimeController.batch.vue @@ -1,17 +1,25 @@ diff --git a/source/vue/components/TimeController.realtime.vue b/source/vue/components/TimeController.realtime.vue index 444cdaf046..bb3864bb39 100644 --- a/source/vue/components/TimeController.realtime.vue +++ b/source/vue/components/TimeController.realtime.vue @@ -1,6 +1,11 @@