BUG FIX: EndDates on the timeSeries settings were not linked across
[wrfxweb.git] / fdds / js / util.js
blobb69beac4309229dc5914c45a321fd343eacd80cb
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.
6 *
7 * Contents
8 * - Constants block
9 * - SetURL block
10 * - Debounce block
11 * - TimeConversion block
12 * - CreateDomElements block
13 * - Color block
14 * - Drag Elements block
18 /** ===== Constants block */
19 export const CLIENT_WIDTH = document.body.clientWidth;
20 export const IS_MOBILE = CLIENT_WIDTH < 769;
22 /** ===== SetURL block ===== */
23 export function setURL() {
24 let historyData = {};
25 let urlVars = '';
27 const addData = (key, data) => {
28 if (data) {
29 historyData[key] = data;
30 urlVars += '&' + key + '=' + data;
34 zoomToURL(addData);
35 panToURL(addData);
36 jobIdToURL(addData);
37 domainToURL(addData);
38 startDateToURL(addData);
39 endDateToURL(addData);
40 timestampToURL(addData);
41 addedLayersToURL(addData);
42 opacityToURL(addData);
44 if (urlVars != '') {
45 urlVars = '?' + urlVars.substr(1);
46 history.pushState(historyData, 'Data', urlVars);
50 function zoomToURL(addData) {
51 let zoom = map.getZoom();
52 addData('zoom', zoom);
55 function panToURL(addData) {
56 let center = map.getCenter();
57 let pan = center.lat.toFixed(2) + ',' + center.lng.toFixed(2);
58 addData('pan', pan);
61 function jobIdToURL(addData) {
62 let currentSimulation = simVars.currentSimulation;
63 addData('job_id', currentSimulation);
66 function domainToURL(addData) {
67 let currentDomain = controllers.currentDomain.getValue();
68 let domainInstances = controllers.domainInstance.getValue();
69 if (domainInstances != null && domainInstances.length > 0 && currentDomain != domainInstances[0]) {
70 addData('domain', currentDomain);
74 function startDateToURL(addData) {
75 let startDate = controllers.startDate.getValue();
76 if (startDate != simVars.sortedTimestamps[0]) {
77 addData('startDate', utcToLocal(startDate));
81 function endDateToURL(addData) {
82 let endDate = controllers.endDate.getValue();
83 let nTimestamps = simVars.sortedTimestamps.length;
84 if (endDate != simVars.sortedTimestamps[nTimestamps - 1]) {
85 addData('endDate', utcToLocal(endDate));
89 function timestampToURL(addData) {
90 let timestamp = controllers.currentTimestamp.getValue();
91 if (timestamp != startDate) {
92 addData('timestamp', utcToLocal(timestamp));
96 function addedLayersToURL(addData) {
97 let rasterURL = simVars.overlayOrder.join(',');
98 addData('rasters', rasterURL);
101 function opacityToURL(addData) {
102 let opacity = controllers.opacity.getValue();
103 if (opacity != 0.5) {
104 addData('opacity', opacity);
108 map.on('zoomend', function() {
109 setURL();
112 map.on('moveend', function() {
113 setURL();
116 /** ===== Debounce block ===== */
117 /** Executes function with a maximum rate of delay. */
118 export function debounceInIntervals(callback, delay) {
119 let timeout;
120 return function(args=null) {
121 if (timeout) {
122 return;
124 callback(args);
125 const callbackInIntervals = () => {
126 timeout = null;
128 timeout = setTimeout(callbackInIntervals, delay);
132 /** Executes a function once at the end of an update cycle lasting delay. */
133 export function debounce(callback, delay) {
134 let timeout;
135 return function(args=null) {
136 if (timeout) {
137 clearTimeout(timeout);
139 timeout = setTimeout(() => callback(args), delay);
143 /** ===== TimeConversion block ===== */
144 /** Function to convert UTC timestamp to PT timestamp. */
145 export function utcToLocal(utcTime) {
146 if (!utcTime) {
147 return;
149 let timezone = 'America/Los_Angeles';
150 let localTime = dayjs(utcTime.replace('_', 'T') + 'Z');
152 return localTime.format('YYYY-MM-DD HH:mm:ss', {timeZone: timezone});
155 export function localToUTC(localTime) {
156 if (!localTime) {
157 return;
159 let timezone = 'America/Los_Angeles';
160 let localTimeDayJS = dayjs(localTime).tz(timezone);
161 let utcTime = localTimeDayJS.tz('UTC');
163 return utcTime.format('YYYY-MM-DD_HH:mm:ss');
166 export function daysBetween(timestamp1, timestamp2) {
167 let date1 = dayjs(timestamp1);
168 let date2 = dayjs(timestamp2);
169 let diff = date1.diff(date2, 'day');
170 return Math.abs(diff);
173 /** ===== CreateDomElements block ===== */
174 export function createOption(timeStamp, utcValue) {
175 let option = document.createElement('option');
176 option.value = timeStamp;
177 let innerText = utcValue ? utcToLocal(timeStamp) : timeStamp;
178 option.innerText = innerText;
179 return option;
182 export function createElement(id=null, className=null) {
183 const div = document.createElement('div');
184 if (id) {
185 div.id = id;
187 if (className) {
188 div.className = className;
190 return div;
193 /** Creates the htmlElement for each checkbox in the LayerController. */
194 export function buildCheckBox(id, type, name, checked, callback, args=null) {
195 let div = document.createElement('div');
196 div.className = 'layer-checkbox';
198 const input = document.createElement('input');
199 input.id = id;
200 input.name = name;
201 input.type = type;
202 input.checked = checked;
203 input.onclick = () => {
204 callback(args);
207 let label = document.createElement('label');
208 label.for = id;
209 label.innerText = id;
210 div.appendChild(input);
211 div.appendChild(label);
212 return div;
215 export function linkSelects(selectStart, selectEnd) {
216 selectStart.childNodes.forEach(startOption => {
217 startOption.disabled = false;
218 if (startOption.value > selectEnd.value) {
219 startOption.disabled = true;
222 selectEnd.childNodes.forEach(endOption => {
223 endOption.disabled = false;
224 if (endOption.value < selectStart.value) {
225 endOption.disabled = true;
230 /** ===== Color block ===== */
231 // pulled from https://www.w3docs.com/snippets/javascript/how-to-convert-rgb-to-hex-and-vice-versa.html
232 export function rgbToHex(r, g, b) {
233 return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
236 export function darkenHex(hex) {
237 let darkenedHex = '#';
238 for (let decimal of hex.substr(1)) {
239 switch (decimal) {
240 case 'f':
241 darkenedHex += 'd';
242 break;
243 case 'e':
244 darkenedHex += 'c';
245 break;
246 case 'd':
247 darkenedHex += 'b';
248 break;
249 case 'c':
250 darkenedHex += 'a';
251 break;
252 case 'b':
253 darkenedHex += '0';
254 break;
255 case 'a':
256 darkenedHex += '8';
257 break;
258 case '0':
259 darkenedHex += 'e';
260 break;
261 case '1':
262 darkenedHex += 'f';
263 break;
264 default:
265 darkenedHex += Number(decimal) - 2;
268 return darkenedHex;
271 /** ===== DragElements block ===== */
272 /** A custom double click implementation needed to handle double clicking on ios. */
273 export function doubleClick(elmnt, doubleClickFunction) {
274 const DOUBLE_CLICK_MS = 200;
275 const MAX_DOUBLE_CLICK_DIST = 30;
276 let timeout = null;
277 let previousE;
278 elmnt.addEventListener('pointerdown', (e) => {
279 if (timeout != null) {
280 let xDiff = Math.abs(e.clientX - previousE.clientX);
281 let yDiff = Math.abs(e.clientY - previousE.clientY);
282 if ((xDiff + yDiff) > MAX_DOUBLE_CLICK_DIST) {
283 timeout = null;
284 previousE = null;
285 return;
287 e.stopPropagation();
288 clearTimeout(timeout);
289 timeout = null;
290 previousE = null;
291 doubleClickFunction(e);
292 } else {
293 previousE = e;
294 timeout = setTimeout(() => {
295 timeout = null;
296 }, DOUBLE_CLICK_MS);
301 /** Makes given element draggable from sub element with id 'subID' */
302 export function dragElement(elmnt, subID='', mobileEnabled=false) {
303 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
304 let elmntLeft = 0, elmntTop = 0;
305 let clientWidth = document.body.clientWidth, clientHeight = document.body.clientHeight;
306 if (IS_MOBILE && !mobileEnabled) {
307 return;
309 let draggableElement = document.getElementById(elmnt.id);
310 if (subID != '') {
311 draggableElement = document.getElementById(subID);
313 // document.getElementById(elmnt.id + subID).onpointerdown = dragMouseDown;
314 // draggableElement.onpointerdown = dragMouseDown;
315 draggableElement.addEventListener('pointerdown', dragMouseDown);
316 window.addEventListener('resize', () => {
317 let offsetLeft = clientWidth - document.body.clientWidth;
318 if (elmntLeft != 0 && elmnt.offsetLeft + (elmnt.clientWidth / 2) > (document.body.clientWidth / 2) && (elmntLeft - offsetLeft) > 0) {
319 elmntLeft = elmntLeft - offsetLeft;
320 elmnt.style.left = elmntLeft + 'px';
322 let offsetTop = clientHeight - document.body.clientHeight;
323 if (elmntTop != 0 && elmnt.offsetTop + (elmnt.clientHeight / 2) > (document.body.clientHeight / 2) && (elmntTop - offsetTop) > 0 && (elmntTop - offsetTop + elmnt.clientHeight) < document.body.clientHeight) {
324 elmntTop = elmntTop - offsetTop;
325 elmnt.style.top = elmntTop + 'px';
327 clientWidth = document.body.clientWidth;
328 clientHeight = document.body.clientHeight;
331 function dragMouseDown(e) {
332 document.body.classList.add('grabbing');
333 e = e || window.event;
334 e.preventDefault();
335 e.stopPropagation();
336 // get the mouse cursor position at startup:
337 pos3 = e.clientX;
338 pos4 = e.clientY;
339 document.onpointerup = closeDragElement;
340 // call a function whenever the cursor moves:
341 document.onpointermove = elementDrag;
344 function elementDrag(e) {
345 e = e || window.event;
346 e.preventDefault();
347 e.stopPropagation();
348 // calculate the new cursor position:
349 pos1 = pos3 - e.clientX;
350 pos2 = pos4 - e.clientY;
351 pos3 = e.clientX;
352 pos4 = e.clientY;
353 if (elmntLeft == 0) {
354 elmntLeft = elmnt.offsetLeft;
355 elmntTop = elmnt.offsetTop;
357 // set the element's new position:
358 if (Math.abs(pos1) >= 1 && elmntLeft - pos1 > 0 && elmntLeft + elmnt.clientWidth - pos1 < clientWidth) {
359 elmntLeft = elmntLeft - pos1;
360 elmnt.style.left = elmntLeft + 'px';
362 if (Math.abs(pos2) >= 1 && elmntTop - pos2 > 0 && elmntTop + elmnt.clientHeight - pos2 < clientHeight) {
363 elmntTop = elmntTop - pos2;
364 elmnt.style.top = elmntTop + 'px';
368 function closeDragElement() {
369 // stop moving when mouse button is released:
370 document.body.classList.remove('grabbing');
371 document.onpointerup = null;
372 document.onpointermove = null;