REFACTOR: Refactors buildCheckBox util function to take an object
[wrfxweb.git] / fdds / js / util.js
blob5022ac33ebf8fa10208b2749b302360fe4a4bbe9
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;
21 export var ELEMENT_FOCUSED = false;
23 /** ===== SetURL block ===== */
24 export function setURL() {
25 let historyData = {};
26 let urlVars = '';
28 const addData = (key, data) => {
29 if (data) {
30 historyData[key] = data;
31 urlVars += '&' + key + '=' + data;
35 zoomToURL(addData);
36 panToURL(addData);
37 jobIdToURL(addData);
38 domainToURL(addData);
39 startDateToURL(addData);
40 endDateToURL(addData);
41 timestampToURL(addData);
42 addedLayersToURL(addData);
43 opacityToURL(addData);
45 if (urlVars != '') {
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);
59 addData('pan', pan);
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() {
110 setURL();
113 map.on('moveend', function() {
114 setURL();
117 /** ===== Debounce block ===== */
118 /** Executes function with a maximum rate of delay. */
119 export function debounceInIntervals(callback, delay) {
120 let timeout;
121 return function(args=null) {
122 if (timeout) {
123 return;
125 callback(args);
126 const callbackInIntervals = () => {
127 timeout = null;
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) {
135 let timeout;
136 return function(args=null) {
137 if (timeout) {
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) {
147 if (!utcTime) {
148 return;
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) {
157 if (!localTime) {
158 return;
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;
180 return option;
183 export function createElement(id=null, className=null) {
184 const div = document.createElement('div');
185 if (id) {
186 div.id = id;
188 if (className) {
189 div.className = className;
191 return div;
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');
200 input.id = id;
201 input.name = name;
202 input.type = type;
203 input.checked = checked;
204 input.onclick = () => {
205 callback(args);
208 let label = document.createElement('label');
209 label.for = id;
210 label.innerText = text;
211 div.appendChild(input);
212 div.appendChild(label);
213 return div;
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)) {
240 switch (decimal) {
241 case 'f':
242 darkenedHex += 'd';
243 break;
244 case 'e':
245 darkenedHex += 'c';
246 break;
247 case 'd':
248 darkenedHex += 'b';
249 break;
250 case 'c':
251 darkenedHex += 'a';
252 break;
253 case 'b':
254 darkenedHex += '0';
255 break;
256 case 'a':
257 darkenedHex += '8';
258 break;
259 case '0':
260 darkenedHex += 'e';
261 break;
262 case '1':
263 darkenedHex += 'f';
264 break;
265 default:
266 darkenedHex += Number(decimal) - 2;
269 return darkenedHex;
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;
286 let timeout = null;
287 let previousE;
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) {
293 timeout = null;
294 previousE = null;
295 return;
297 e.stopPropagation();
298 clearTimeout(timeout);
299 timeout = null;
300 previousE = null;
301 doubleClickFunction(e);
302 } else {
303 previousE = e;
304 timeout = setTimeout(() => {
305 timeout = null;
306 }, DOUBLE_CLICK_MS);
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) {
317 return;
319 let draggableElement = document.getElementById(elmnt.id);
320 if (subID != '') {
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;
344 e.preventDefault();
345 e.stopPropagation();
346 // get the mouse cursor position at startup:
347 pos3 = e.clientX;
348 pos4 = e.clientY;
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;
356 e.preventDefault();
357 e.stopPropagation();
358 // calculate the new cursor position:
359 pos1 = pos3 - e.clientX;
360 pos2 = pos4 - e.clientY;
361 pos3 = e.clientX;
362 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;