1 import { controllers
} from './components/Controller.js';
2 import { simVars
} from './simVars.js';
3 import { map
} from './map.js';
5 /** Utility functions that can be imported and used in components from anywhere.
11 * - TimeConversion block
12 * - CreateDomElements block
14 * - Drag Elements block
18 /** ===== Constants block */
19 export const CLIENT_WIDTH
= document
.body
.clientWidth
;
20 export const IS_MOBILE
= CLIENT_WIDTH
< 769;
21 export var ELEMENT_FOCUSED
= false;
23 /** ===== SetURL block ===== */
24 export function setURL() {
28 const addData
= (key
, data
) => {
30 historyData
[key
] = data
;
31 urlVars
+= '&' + key
+ '=' + data
;
39 startDateToURL(addData
);
40 endDateToURL(addData
);
41 timestampToURL(addData
);
42 addedLayersToURL(addData
);
43 opacityToURL(addData
);
46 urlVars
= '?' + urlVars
.substr(1);
47 history
.pushState(historyData
, 'Data', urlVars
);
51 function zoomToURL(addData
) {
52 let zoom
= map
.getZoom();
53 addData('zoom', zoom
);
56 function panToURL(addData
) {
57 let center
= map
.getCenter();
58 let pan
= center
.lat
.toFixed(2) + ',' + center
.lng
.toFixed(2);
62 function jobIdToURL(addData
) {
63 let currentSimulation
= simVars
.currentSimulation
;
64 addData('job_id', currentSimulation
);
67 function domainToURL(addData
) {
68 let currentDomain
= controllers
.currentDomain
.getValue();
69 let domainInstances
= controllers
.domainInstance
.getValue();
70 if (domainInstances
!= null && domainInstances
.length
> 0 && currentDomain
!= domainInstances
[0]) {
71 addData('domain', currentDomain
);
75 function startDateToURL(addData
) {
76 let startDate
= controllers
.startDate
.getValue();
77 if (startDate
!= simVars
.sortedTimestamps
[0]) {
78 addData('startDate', utcToLocal(startDate
));
82 function endDateToURL(addData
) {
83 let endDate
= controllers
.endDate
.getValue();
84 let nTimestamps
= simVars
.sortedTimestamps
.length
;
85 if (endDate
!= simVars
.sortedTimestamps
[nTimestamps
- 1]) {
86 addData('endDate', utcToLocal(endDate
));
90 function timestampToURL(addData
) {
91 let timestamp
= controllers
.currentTimestamp
.getValue();
92 if (timestamp
!= startDate
) {
93 addData('timestamp', utcToLocal(timestamp
));
97 function addedLayersToURL(addData
) {
98 let rasterURL
= simVars
.overlayOrder
.join(',');
99 addData('rasters', rasterURL
);
102 function opacityToURL(addData
) {
103 let opacity
= controllers
.opacity
.getValue();
104 if (opacity
!= 0.5) {
105 addData('opacity', opacity
);
109 map
.on('zoomend', function() {
113 map
.on('moveend', function() {
117 /** ===== Debounce block ===== */
118 /** Executes function with a maximum rate of delay. */
119 export function debounceInIntervals(callback
, delay
) {
121 return function(args
=null) {
126 const callbackInIntervals
= () => {
129 timeout
= setTimeout(callbackInIntervals
, delay
);
133 /** Executes a function once at the end of an update cycle lasting delay. */
134 export function debounce(callback
, delay
) {
136 return function(args
=null) {
138 clearTimeout(timeout
);
140 timeout
= setTimeout(() => callback(args
), delay
);
144 /** ===== TimeConversion block ===== */
145 /** Function to convert UTC timestamp to PT timestamp. */
146 export function utcToLocal(utcTime
) {
150 let timezone
= 'America/Los_Angeles';
151 let localTime
= dayjs(utcTime
.replace('_', 'T') + 'Z');
153 return localTime
.format('YYYY-MM-DD HH:mm:ss', {timeZone
: timezone
});
156 export function localToUTC(localTime
) {
160 let timezone
= 'America/Los_Angeles';
161 let localTimeDayJS
= dayjs(localTime
).tz(timezone
);
162 let utcTime
= localTimeDayJS
.tz('UTC');
164 return utcTime
.format('YYYY-MM-DD_HH:mm:ss');
167 export function daysBetween(timestamp1
, timestamp2
) {
168 let date1
= dayjs(timestamp1
);
169 let date2
= dayjs(timestamp2
);
170 let diff
= date1
.diff(date2
, 'day');
171 return Math
.abs(diff
);
174 /** ===== CreateDomElements block ===== */
175 export function createOption(timeStamp
, utcValue
) {
176 let option
= document
.createElement('option');
177 option
.value
= timeStamp
;
178 let innerText
= utcValue
? utcToLocal(timeStamp
) : timeStamp
;
179 option
.innerText
= innerText
;
183 export function createElement(id
=null, className
=null) {
184 const div
= document
.createElement('div');
189 div
.className
= className
;
194 /** Creates the htmlElement for each checkbox in the LayerController. */
195 export function buildCheckBox({id
, type
, name
, checked
, callback
, args
=null, text
}) {
196 let div
= document
.createElement('div');
197 div
.className
= 'layer-checkbox';
199 const input
= document
.createElement('input');
203 input
.checked
= checked
;
204 input
.onclick
= () => {
208 let label
= document
.createElement('label');
210 label
.innerText
= text
;
211 div
.appendChild(input
);
212 div
.appendChild(label
);
216 export function linkSelects(selectStart
, selectEnd
) {
217 selectStart
.childNodes
.forEach(startOption
=> {
218 startOption
.disabled
= false;
219 if (startOption
.value
> selectEnd
.value
) {
220 startOption
.disabled
= true;
223 selectEnd
.childNodes
.forEach(endOption
=> {
224 endOption
.disabled
= false;
225 if (endOption
.value
< selectStart
.value
) {
226 endOption
.disabled
= true;
231 /** ===== Color block ===== */
232 // pulled from https://www.w3docs.com/snippets/javascript/how-to-convert-rgb-to-hex-and-vice-versa.html
233 export function rgbToHex(r
, g
, b
) {
234 return "#" + ((1 << 24) + (r
<< 16) + (g
<< 8) + b
).toString(16).slice(1);
237 export function darkenHex(hex
) {
238 let darkenedHex
= '#';
239 for (let decimal of hex
.substr(1)) {
266 darkenedHex
+= Number(decimal) - 2;
272 /** ===== DragElements block ===== */
273 export function isolateFocus(element
) {
274 element
.onfocus
= () => {
275 ELEMENT_FOCUSED
= true;
277 element
.onblur
= () => {
278 ELEMENT_FOCUSED
= false;
282 /** A custom double click implementation needed to handle double clicking on ios. */
283 export function doubleClick(elmnt
, doubleClickFunction
) {
284 const DOUBLE_CLICK_MS
= 200;
285 const MAX_DOUBLE_CLICK_DIST
= 30;
288 elmnt
.addEventListener('pointerdown', (e
) => {
289 if (timeout
!= null) {
290 let xDiff
= Math
.abs(e
.clientX
- previousE
.clientX
);
291 let yDiff
= Math
.abs(e
.clientY
- previousE
.clientY
);
292 if ((xDiff
+ yDiff
) > MAX_DOUBLE_CLICK_DIST
) {
298 clearTimeout(timeout
);
301 doubleClickFunction(e
);
304 timeout
= setTimeout(() => {
311 /** Makes given element draggable from sub element with id 'subID' */
312 export function dragElement(elmnt
, subID
='', mobileEnabled
=false) {
313 let pos1
= 0, pos2
= 0, pos3
= 0, pos4
= 0;
314 let elmntLeft
= 0, elmntTop
= 0;
315 let clientWidth
= document
.body
.clientWidth
, clientHeight
= document
.body
.clientHeight
;
316 if (IS_MOBILE
&& !mobileEnabled
) {
319 let draggableElement
= document
.getElementById(elmnt
.id
);
321 draggableElement
= document
.getElementById(subID
);
323 // document.getElementById(elmnt.id + subID).onpointerdown = dragMouseDown;
324 // draggableElement.onpointerdown = dragMouseDown;
325 draggableElement
.addEventListener('pointerdown', dragMouseDown
);
326 window
.addEventListener('resize', () => {
327 let offsetLeft
= clientWidth
- document
.body
.clientWidth
;
328 if (elmntLeft
!= 0 && elmnt
.offsetLeft
+ (elmnt
.clientWidth
/ 2) > (document
.body
.clientWidth
/ 2) && (elmntLeft
- offsetLeft
) > 0) {
329 elmntLeft
= elmntLeft
- offsetLeft
;
330 elmnt
.style
.left
= elmntLeft
+ 'px';
332 let offsetTop
= clientHeight
- document
.body
.clientHeight
;
333 if (elmntTop
!= 0 && elmnt
.offsetTop
+ (elmnt
.clientHeight
/ 2) > (document
.body
.clientHeight
/ 2) && (elmntTop
- offsetTop
) > 0 && (elmntTop
- offsetTop
+ elmnt
.clientHeight
) < document
.body
.clientHeight
) {
334 elmntTop
= elmntTop
- offsetTop
;
335 elmnt
.style
.top
= elmntTop
+ 'px';
337 clientWidth
= document
.body
.clientWidth
;
338 clientHeight
= document
.body
.clientHeight
;
341 function dragMouseDown(e
) {
342 document
.body
.classList
.add('grabbing');
343 e
= e
|| window
.event
;
346 // get the mouse cursor position at startup:
349 document
.onpointerup
= closeDragElement
;
350 // call a function whenever the cursor moves:
351 document
.onpointermove
= elementDrag
;
354 function elementDrag(e
) {
355 e
= e
|| window
.event
;
358 // calculate the new cursor position:
359 pos1
= pos3
- e
.clientX
;
360 pos2
= pos4
- e
.clientY
;
363 if (elmntLeft
== 0) {
364 elmntLeft
= elmnt
.offsetLeft
;
365 elmntTop
= elmnt
.offsetTop
;
367 // set the element's new position:
368 if (Math
.abs(pos1
) >= 1 && elmntLeft
- pos1
> 0 && elmntLeft
+ elmnt
.clientWidth
- pos1
< clientWidth
) {
369 elmntLeft
= elmntLeft
- pos1
;
370 elmnt
.style
.left
= elmntLeft
+ 'px';
372 if (Math
.abs(pos2
) >= 1 && elmntTop
- pos2
> 0 && elmntTop
+ elmnt
.clientHeight
- pos2
< clientHeight
) {
373 elmntTop
= elmntTop
- pos2
;
374 elmnt
.style
.top
= elmntTop
+ 'px';
378 function closeDragElement() {
379 // stop moving when mouse button is released:
380 document
.body
.classList
.remove('grabbing');
381 document
.onpointerup
= null;
382 document
.onpointermove
= null;