Projekt

Obecné

Profil

Stáhnout (20.1 KB) Statistiky
| Větev: | Revize:
1
/* global L */
2
/* global $ */
3

    
4
var mymap
5
var heatmapLayer = null
6
var marksLayer = null
7

    
8
// values for arrow keys
9
const arrowKeyLEFT = 37
10
const arrowKeyRIGHT = 39
11

    
12
var startX = 49.7248
13
var startY = 13.3521
14
var startZoom = 17
15

    
16
var dataSourceRoute
17
let positionsSourceRoute
18

    
19
let currentTime
20

    
21
let currentDate
22

    
23
var timer
24
var isAnimationRunning = false
25
var data = []
26

    
27
var info = []
28
let currentInfo = 0
29

    
30
// dictionary for names of datasets
31
const datasetDictNameDisplayName = {}
32
var datasetSelected = []
33

    
34
// data only for one day
35
let lockedDay = false
36

    
37
// loading information for async operations
38
let loading = 0
39

    
40
// default loader showup delay
41
const defaultLoaderDelay = 1000
42

    
43
// marks for all datasets
44
const dataSourceMarks = {}
45

    
46
const globalMarkersHolder = {}
47
// all marker from which popup was removed
48
// contains: {key:[L.circle,L.pupup]}
49
// key: x and y, x + '' + y string
50
let globalMarkersChanged = {}
51

    
52
const loadingCallbackNested = (func, delay) => {
53
  setTimeout(() => {
54
    func(loading)
55
    if (loading) {
56
      loadingCallbackNested(func, delay)
57
    }
58
  }, delay)
59
}
60
const loadingY = (delay = defaultLoaderDelay) => {
61
  loading++
62
  // check after nms if there is something that is loading
63
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
64
}
65
const loadingN = (delay = defaultLoaderDelay) => {
66
  loading--
67
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
68
}
69

    
70
const changeCurrentTime = (time = null) => {
71
  if (time !== null) {
72
    currentTime = time
73
  } else {
74
    currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val())
75
  }
76
}
77

    
78
const changeCurrentDate = (date = null) => {
79
  if (date) {
80
    currentDate = new Date(date)
81
  } else {
82
    currentDate = new Date($('#date').val())
83
  }
84

    
85
  $('#date').val(currentDateToString())
86
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
87

    
88
  data = []
89
}
90
const currentDayToString = () => {
91
  const day = currentDate.getDate()
92
  return day > 9 ? `${day}` : `0${day}`
93
}
94
const currentMonthToString = () => {
95
  const month = currentDate.getMonth() + 1
96
  return month > 9 ? `${month}` : `0${month}`
97
}
98
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}`
99
const addDayToCurrentDate = (day) => {
100
  currentDate.setDate(currentDate.getDate() + day)
101
  changeCurrentDate(currentDate)
102
}
103
const toggleDayLock = () => {
104
  lockedDay = !lockedDay
105
  $('#player-date').toggleClass('lock')
106
}
107

    
108
const fetchByNameDate = async (baseRoute, name, date, currentTime) => {
109
  const headers = new Headers()
110
  const myRequest = new Request(baseRoute + '/' + name + '/' + date + '/' + currentTime, {
111
    method: 'GET',
112
    headers: headers
113
  })
114
  const beforeJson = await fetch(myRequest)
115
  return beforeJson.json()
116
}
117

    
118
const fetchDataSourceMarks = async (positionRoute, datasetName) => {
119
  const headers = new Headers()
120
  const myRequest = new Request(positionRoute + '/' + datasetName, {
121
    method: 'GET',
122
    headers: headers
123
  })
124
  const beforeJson = await fetch(myRequest)
125
  return beforeJson.json()
126
}
127

    
128
const genPopUpControlButtons = (currentPage, numPages, onNextClick, onPreviousClick) => ({
129
  previousButton: '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>',
130
  nextButton: '<button id="next-info-btn" class="circle-button next" onclick="nextInfo()"></button>',
131
  posInfo: `<div id="count-info">${currentPage} z ${numPages}</div>`
132
})
133

    
134
const genPopUpControls = (controls) => {
135
  return `<div class="popup-controls">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
136
}
137

    
138
const genMultipleDatasetsPopUp = (sum, currentPos, maxPos, datasetName) => {
139
  const popupHeader = `<strong id="dataset-info">${datasetName}</strong>`
140
  const popupData = `<div id="number-info"><span id="digit-info">${sum}</span></div>`
141
  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
142

    
143
  return `
144
  ${popupHeader}
145
  ${popupData}
146
  ${genPopUpControls([previousButton, posInfo, nextButton])}
147
  `
148
}
149

    
150
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
151
  autoPan: false,
152
  className: className
153
}).setLatLng([lat / num, lng / num])
154

    
155
const genPopUp = (datasetName, place, count, sum, currentPos, maxPos) => {
156
  const popupHeader = `
157
    <strong>${datasetName}</strong>
158
    <div id="place-info">${place}</div>`
159
  const popupData = `
160
    <div id="number-info">
161
      <span id="digit-info">${count}</span>
162
      <span id="total-info">${(sum && (sum != count)) ? '/' + sum : ''}</span>
163
    </div>`
164
  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
165

    
166
  return `
167
  ${popupHeader}
168
  ${popupData}
169
  ${genPopUpControls(maxPos > 1 ? [previousButton, posInfo, nextButton] : null)}
170
  `
171
}
172

    
173
const onCheckboxClicked = async (checkbox) => {
174
  if ($(checkbox).prop('checked')) {
175
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
176
    changeUrl()
177
  } else {
178
    loadCheckboxDatasetNameData()
179

    
180
    data.forEach((item, index) => {
181
      Object.keys(item).forEach((datasetName) => {
182
        if (datasetName === $(checkbox).val()) {
183
          delete data[index][datasetName]
184
        }
185
      })
186
      drawHeatmap(data[currentTime])
187
    })
188

    
189
    changeUrl()
190
  }
191
}
192

    
193
const onArrowLeftRightKeysDownRegister = () => {
194
  $(document).keydown(function (e) {
195
    const { which } = e
196
    if (which === arrowKeyLEFT) {
197
      previous()
198
    } else if (which === arrowKeyRIGHT) {
199
      next()
200
    }
201
    e.preventDefault()
202
  })
203
}
204

    
205
const debounce = (func, delay) => {
206
  let inDebounce
207
  return function () {
208
    const context = this
209
    const args = arguments
210
    clearTimeout(inDebounce)
211
    inDebounce = setTimeout(() => func.apply(context, args), delay)
212
  }
213
}
214

    
215
const onValueChangeRegister = () => {
216
  $('#date').change(function () {
217
    changeCurrentDate($(this).val())
218
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
219
    changeUrl()
220
  })
221

    
222
  $('#dropdown-time input[type="radio"]').each(function () {
223
    $(this).change(function () {
224
      changeHour(parseInt($(this).val()))
225
      drawHeatmap(data[currentTime])
226
    })
227
  })
228

    
229
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
230
    $(this).change(
231
      debounce(() => onCheckboxClicked(this), 1000)
232
    )
233
  })
234
}
235

    
236
/**
237
 * Initialize leaflet map on start position which can be default or set based on user action
238
 */
239
// eslint-disable-next-line no-unused-vars
240
function initMap () {
241
  startX = localStorage.getItem('lat') || startX
242
  startY = localStorage.getItem('lng') || startY
243
  startZoom = localStorage.getItem('zoom') || startZoom
244

    
245
  mymap = L.map('heatmap').setView([startX, startY], startZoom)
246

    
247
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
248
    attribution: '',
249
    maxZoom: 19
250
  }).addTo(mymap)
251

    
252
  mymap.on('click', showInfo)
253
}
254

    
255
const getInfoLength = () => {
256
  const infoKeys = Object.keys(info)
257
  if (infoKeys.length === 1) {
258
    // return number of records in one dataset (one dataset in area)
259
    return info[infoKeys[0]].items.length
260
  }
261
  // return number of datasets (agregation of all datasets in area)
262
  return infoKeys.length
263
}
264

    
265
const getElFromObjectInfo = (position) => {
266
  const keys = Object.keys(info)
267
  return info[keys[position]]
268
}
269

    
270
const hasInfoMultipleDatasets = () => {
271
  return Object.keys(info).length > 1
272
}
273

    
274
function showInfo (e) {
275
  info = []
276
  currentInfo = 0
277

    
278
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
279
  // Todo change to variable - it is used in heatmap init
280
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
281
  const radius = 25 * stile / 256
282

    
283
  let i = 0
284
  let lat = 0
285
  let lng = 0
286

    
287
  let total = 0
288

    
289
  const datasetsInRadius = {}
290

    
291
  Object.keys(data[currentTime]).forEach((key) => {
292
    const namedData = data[currentTime][key]
293
    namedData.items.forEach(element => {
294
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
295
        lat += element.x
296
        lng += element.y
297
        info[i] = { place: element.place, number: element.number, datasetName: key }
298
        total += parseInt(element.number)
299
        i++
300
        datasetsInRadius[key] = true
301
      }
302
    })
303
  })
304

    
305
  // Process info for more then one dataset
306

    
307
  info = info.reduce((acc, item) => {
308
    if (!acc[item.datasetName]) {
309
      acc[item.datasetName] = {
310
        items: [],
311
        number: 0,
312
        datasetName: item.datasetName
313
      }
314
    }
315

    
316
    acc[item.datasetName].items.push(item)
317
    acc[item.datasetName].number += Number(item.number)
318
    return acc
319
  }, {})
320

    
321
  // There is one dataset
322

    
323
  const numDatasets = Object.keys(datasetsInRadius).length
324

    
325
  if (!numDatasets) {
326
    return
327
  }
328

    
329
  if (numDatasets === 1) {
330
    const infoDict = getElFromObjectInfo(0)
331
    const info_ = infoDict.items
332
    const { place, number } = info_[currentInfo]
333
    prepareLayerPopUp(lat, lng, i, `popup-${infoDict.datasetName}`)
334
      .setContent(genPopUp(datasetDictNameDisplayName[infoDict.datasetName], place, number, total, currentInfo + 1, info_.length))
335
      .openOn(mymap)
336

    
337
    if (info_.length === 1) {
338
      $('#previous-info-btn').prop('disabled', true)
339
      $('#next-info-btn').prop('disabled', true)
340
      $('.popup-controls').hide()
341
    }
342
  } else {
343
    const { datasetName, number } = getElFromObjectInfo(currentInfo)
344

    
345
    prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
346
      .setContent(genMultipleDatasetsPopUp(number, currentInfo + 1, getInfoLength(), datasetDictNameDisplayName[datasetName]))
347
      .openOn(mymap)
348
  }
349
}
350

    
351
// eslint-disable-next-line no-unused-vars
352
function previousInfo () {
353
  const infoLength = getInfoLength()
354
  const previousCurrentInfo = currentInfo
355

    
356
  currentInfo = (currentInfo + infoLength - 1) % infoLength
357
  displayInfoText(previousCurrentInfo)
358
}
359

    
360
// eslint-disable-next-line no-unused-vars
361
function nextInfo () {
362
  const infoLength = getInfoLength()
363
  const previousCurrentInfo = currentInfo
364

    
365
  currentInfo = (currentInfo + 1) % infoLength
366
  displayInfoText(previousCurrentInfo)
367
}
368

    
369
function displayInfoText (previousInfoNum) {
370
  const previousInfo = hasInfoMultipleDatasets() ? getElFromObjectInfo(previousInfoNum) : getElFromObjectInfo(0).items[previousInfoNum]
371
  const info_ = hasInfoMultipleDatasets() ? getElFromObjectInfo(currentInfo) : getElFromObjectInfo(0).items[currentInfo]
372
  const infoLength = getInfoLength()
373
  const datasetInfo = $('#dataset-info')
374

    
375
  if (datasetInfo) {
376
    $(datasetInfo).html(datasetDictNameDisplayName[info_.datasetName])
377
  }
378

    
379
  $('#place-info').html(info_.place ? info_.place : info_.datasetName)
380
  $('#digit-info').html(info_.number)
381
  $('#count-info').html(currentInfo + 1 + ' z ' + infoLength)
382

    
383
  $('.leaflet-popup').removeClass(`popup-${previousInfo.datasetName}`)
384
  $('.leaflet-popup').addClass(`popup-${info_.datasetName}`)
385
}
386

    
387
// eslint-disable-next-line no-unused-vars
388
function setMapView (latitude, longitude, zoom) {
389
  localStorage.setItem('lat', latitude)
390
  localStorage.setItem('lng', longitude)
391
  localStorage.setItem('zoom', zoom)
392
  mymap.setView([latitude, longitude], zoom)
393
}
394

    
395
/**
396
 * Change animation start from playing to stopped or the other way round
397
 */
398
// eslint-disable-next-line no-unused-vars
399
function changeAnimationState () {
400
  isAnimationRunning = !isAnimationRunning
401

    
402
  if (isAnimationRunning) {
403
    $('#animate-btn').removeClass('play').addClass('pause')
404
    timer = setInterval(function () { next() }, 800)
405
  } else {
406
    clearTimeout(timer)
407
    $('#animate-btn').removeClass('pause').addClass('play')
408
  }
409
}
410

    
411
// eslint-disable-next-line no-unused-vars
412
async function previous () {
413
  if (loading) {
414
    return
415
  }
416

    
417
  currentTime = (currentTime + 23) % 24
418
  changeHour(currentTime)
419
  mymap.closePopup()
420
  if (!lockedDay && (currentTime === 23)) {
421
    addDayToCurrentDate(-1)
422
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
423
  } else {
424
    drawHeatmap(data[currentTime])
425
  }
426
}
427

    
428
async function next () {
429
  if (loading) {
430
    return
431
  }
432

    
433
  currentTime = (currentTime + 1) % 24
434
  changeHour(currentTime)
435
  mymap.closePopup()
436
  if (!lockedDay && (currentTime === 0)) {
437
    addDayToCurrentDate(1)
438
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
439
  } else {
440
    drawHeatmap(data[currentTime])
441
  }
442
}
443

    
444
/**
445
 * Change browser url based on animation step.
446
 */
447
function changeUrl () {
448
  window.history.pushState(
449
    '',
450
    document.title,
451
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type=' + current, '')}`
452
  )
453
}
454

    
455
function updateHeaderControls () {
456
  $(`#time_${currentTime}`).prop('checked', true)
457
  $('#dropdownMenuButtonTime').html((currentTime < 10 ? '0' : '') + `${currentTime}:00`)
458
}
459

    
460
function setTimeline () {
461
  $('#player-time > span').text(currentTime + ':00')
462
  $('#player-time').attr('class', 'time hour-' + currentTime)
463
}
464
const loadingTimeline = (isLoading) => {
465
  if (isLoading) {
466
    loadingYTimeline()
467
  } else {
468
    loadingNTimeline()
469
  }
470
}
471
const loadingYTimeline = () => {
472
  $('#player-time > .spinner-border').removeClass('d-none')
473
  $('#player-time > span').text('')
474
}
475
const loadingNTimeline = () => {
476
  $('#player-time > .spinner-border').addClass('d-none')
477
  setTimeline()
478
}
479
const onChangeHour = (hour) => {
480
  changeHour(hour)
481
  drawHeatmap(data[currentTime])
482
}
483

    
484
const changeHour = (hour) => {
485
  $('#player-time').removeAttr('style')
486
  changeCurrentTime(hour)
487
  updateHeaderControls()
488
  setTimeline()
489
  changeUrl()
490
}
491

    
492
/**
493
 * Load and display heatmap layer for current data
494
 * @param {string} opendataRoute route to dataset source
495
 * @param {string} positionsRoute  route to dataset postitions source
496
 */
497
// eslint-disable-next-line no-unused-vars
498
async function loadCurrentTimeHeatmap (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) {
499
  loadCheckboxDatasetNameData()
500

    
501
  dataSourceRoute = opendataRoute
502
  positionsSourceRoute = positionsRoute
503
  const allPromises = []
504
  data[currentTime] = {}
505

    
506
  const dataSelectedHandler = async (datasetName) => {
507
    if (!(datasetName in dataSourceMarks)) {
508
      dataSourceMarks[datasetName] = await fetchDataSourceMarks(positionsRoute, datasetName)
509
    }
510
    const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, currentDateToString(), currentTime)
511
    data[currentTime][datasetName] = datasetData
512
  }
513
  datasetSelected.forEach((datasetName) => {
514
    allPromises.push(dataSelectedHandler(datasetName))
515
  })
516

    
517
  loadingY(loaderDelay)
518
  Promise.all(allPromises).then(
519
    () => {
520
      loadingN(0)
521
      drawDataSourceMarks(dataSourceMarks)
522
      drawHeatmap(data[currentTime])
523
      preload(currentTime, 1, currentDateToString())
524
      preload(currentTime, -1, currentDateToString())
525
    }
526
  )
527
}
528

    
529
function drawDataSourceMarks (data) {
530
  if (marksLayer != null) {
531
    mymap.removeLayer(marksLayer)
532
  }
533

    
534
  marksLayer = L.layerGroup()
535

    
536
  Object.keys(data).forEach((key_) => {
537
    for (var key in data[key_]) {
538
      const { x, y, name } = data[key_][key]
539
      const pop =
540
          prepareLayerPopUp(x, y, 1, `popup-${key_}`)
541
            .setContent(genPopUp(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
542
      const newCircle =
543
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
544
          .bindPopup(pop)
545
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
546
      marksLayer.addLayer(
547
        newCircle
548
      )
549
    }
550
  })
551

    
552
  marksLayer.setZIndex(-1).addTo(mymap)
553
}
554

    
555
async function preload (time, change, date) {
556
  loadingY()
557
  for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) {
558
    if (!data[nTime]) {
559
      data[nTime] = {}
560
    }
561

    
562
    datasetSelected.forEach(async (datasetName) => {
563
      if (!data[nTime][datasetName]) {
564
        data[nTime][datasetName] = await fetchByNameDate(dataSourceRoute, datasetName, date, nTime)
565
      }
566
    })
567
  }
568
  loadingN()
569
}
570

    
571
function drawHeatmap (dataRaw) {
572
  // Todo still switched
573
  const dataDict = dataRaw
574
  const mergedPoints = []
575
  let max = 0
576

    
577
  if (Object.keys(globalMarkersChanged).length) {
578
    Object.keys(globalMarkersChanged).forEach(function (key) {
579
      globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
580
    })
581
    globalMarkersChanged = {}
582
  }
583

    
584
  Object.keys(dataDict).forEach((key) => {
585
    const data = dataDict[key]
586
    max = Math.max(max, data.max)
587

    
588
    if (data != null) {
589
    // Bind back popups for markers (we dont know if there is any data for this marker or not)
590
      const points = data.items.map((point) => {
591
        const { x, y, number } = point
592
        const key = x + '' + y
593
        const holder = globalMarkersHolder[key]
594

    
595
        if (!globalMarkersChanged[key] && number) {
596
        // There is data for this marker => unbind popup with zero value
597
          holder[0] = holder[0].unbindPopup()
598
          globalMarkersChanged[key] = holder
599
        }
600

    
601
        return [x, y, number]
602
      })
603
      mergedPoints.push(...points)
604
    } else {
605
      if (heatmapLayer != null) {
606
        mymap.removeLayer(heatmapLayer)
607
      }
608
    }
609
  })
610

    
611
  if (heatmapLayer != null) {
612
    mymap.removeLayer(heatmapLayer)
613
  }
614

    
615
  if (mergedPoints.length) {
616
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
617
  }
618
}
619

    
620
/**
621
 * Checks dataset availibility
622
 * @param {string} route authority for datasets availibility checks
623
 */
624
// eslint-disable-next-line no-unused-vars
625
function checkDataSetsAvailability (route) {
626
  $.ajax({
627
    type: 'POST',
628
    // Todo it might be good idea to change db collections format
629
    url: route + '/' + currentDateToString(),
630
    success: function (result) {
631
      updateAvailableDataSets(result)
632
    }
633
  })
634
}
635

    
636
function updateAvailableDataSets (available) {
637
  let leastOneOptionEnabled = false
638

    
639
  $('#dropdown-dataset .dropdown-item').each(function () {
640
    const input = $(this).find('input')
641
    const inputVal = input[0].value
642

    
643
    if (!(inputVal in available)) {
644
      $(this).addClass('disabled')
645
      $(input).prop('checked', false)
646
    } else {
647
      leastOneOptionEnabled = true
648
      $(this).removeClass('disabled')
649
    }
650
  })
651

    
652
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
653
}
654

    
655
function formatDate (date) {
656
  var day = String(date.getDate())
657
  var month = String(date.getMonth() + 1)
658

    
659
  if (day.length === 1) {
660
    day = '0' + day
661
  }
662

    
663
  if (month.length === 1) {
664
    month = '0' + month
665
  }
666

    
667
  return date.getFullYear() + '-' + month + '-' + day
668
}
669

    
670
// eslint-disable-next-line no-unused-vars
671
function initDatepicker (availableDatesSource) {
672
  var availableDates = ''
673

    
674
  $.ajax({
675
    type: 'GET',
676
    url: availableDatesSource,
677
    success: function (result) {
678
      availableDates = String(result).split(',')
679
    }
680
  }).then(function () {
681
    $('#date').datepicker({
682
      format: 'yyyy-mm-dd',
683
      language: 'cs',
684
      beforeShowDay: function (date) {
685
        if (availableDates.indexOf(formatDate(date)) < 0) {
686
          return { enabled: false, tooltip: 'Žádná data' }
687
        } else {
688
          return { enabled: true }
689
        }
690
      },
691
      autoclose: true
692
    })
693
  })
694
}
695

    
696
function initLocationsMenu () {
697
  var locationsWrapper = '.locations'
698
  var locationsDisplayClass = 'show'
699

    
700
  if ($(window).width() <= 480) {
701
    $(locationsWrapper).removeClass(locationsDisplayClass)
702
  } else {
703
    $(locationsWrapper).addClass(locationsDisplayClass)
704
  }
705
}
706

    
707
function onDocumentReady () {
708
  $('#dropdown-dataset').on('click', function (e) {
709
    e.stopPropagation()
710
  })
711

    
712
  $('#btn-update-heatmap').prop('name', '')
713
  changeCurrentTime()
714
  changeCurrentDate()
715
  onValueChangeRegister()
716
  onArrowLeftRightKeysDownRegister()
717
}
718

    
719
const loadCheckboxDatasetNameData = () => {
720
  datasetSelected = []
721
  $('#dropdown-dataset .dropdown-item').each(function () {
722
    const input = $(this).find('input')
723
    const inputVal = input[0].value
724

    
725
    if (input[0].checked) {
726
      datasetSelected.push(inputVal)
727
    }
728

    
729
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
730
  })
731
}
732

    
733
function dragTimeline () {
734
  const hourElemWidthPx = 26
735

    
736
  const elem = $('#player-time')
737
  const offset = elem.offset().left - elem.parent().offset().left
738

    
739
  elem.draggable({ containment: 'parent', axis: 'x', cursor: 'ew-resize' })
740

    
741
  if (offset >= 0 && offset <= elem.parent().width()) {
742
    const hour = Math.round(offset / hourElemWidthPx)
743

    
744
    elem.attr('class', 'time hour-' + hour)
745
    $('#player-time span').html(hour + ':00')
746

    
747
    onChangeHour(hour)
748
  }
749
}
(2-2/2)