Files
map/webapp/frontend/src/utils.js

250 lines
7.9 KiB
JavaScript

import { state } from './store.js';
import { temperatureFormat, nodesDisconnectedAge, BASE_PATH } from './config.js';
import moment from 'moment';
export const timeSpans = {
'1d': {name: '1 Day', amount: 86400},
'3d': {name: '3 Days', amount: 259200},
'7d': {name: '1 Week', amount: 604800},
'14d': {name: '2 Weeks', amount: 1209600},
'30d': {name: '1 Month', amount: 2592000},
'90d': {name: '90 Days', amount: 7776000},
};
export function getTimeSpans() {
return timeSpans;
};
export function getTimeSpan(value) {
return timeSpans[value];
};
// convert node id to a hex colour
export function getNodeColor(nodeId) {
return "#" + (nodeId & 0x00FFFFFF).toString(16).padStart(6, '0');
};
export function getNodeTextColor(nodeId) {
// extract rgb components
const r = (nodeId & 0xFF0000) >> 16;
const g = (nodeId & 0x00FF00) >> 8;
const b = nodeId & 0x0000FF;
// calculate brightness
const brightness = ((r * 0.299) + (g * 0.587) + (b * 0.114)) / 255;
// determine text color based on brightness
return brightness > 0.5 ? "#000000" : "#FFFFFF";
};
export function getRegionFrequencyRange(regionName) {
// determine lora frequency range based on region_name
// https://github.com/meshtastic/firmware/blob/a4c22321fca6fc8da7bab157c3812055603512ba/src/mesh/RadioInterface.cpp#L21
const regionNameToLoraFrequencyRange = {
"US": "902-928 MHz",
"EU_433": "433-434 MHz",
"EU_868": "869.4-869.65 MHz",
"CN": "470-510 MHz",
"JP": "920.8-927.8 MHz",
"ANZ": "915-928 MHz",
"RU": "868.7-869.2 MHz",
"KR": "920-923 MHz",
"TW": "920-925 MHz",
"IN": "865-867 MHz",
"NZ_865": "864-868 MHz",
"TH": "920-925 MHz",
"UA_433": "433-434.7 MHz",
"UA_868": "868-868.6 MHz",
"MY_433": "433-435 MHz",
"MY_919": "919-924 MHz",
"SG_923": "917-925 MHz",
"LORA_24": "2.4-2.4835 GHz",
"UNSET": "902-928 MHz",
}
return regionNameToLoraFrequencyRange[regionName] ?? null;
};
export function getShareLinkForNode(nodeId) {
return window.location.origin + `/?node_id=${nodeId}`;
};
export function copyShareLinkForNode(nodeId) {
// make sure copy to clipboard is supported
if (!navigator.clipboard || !navigator.clipboard.writeText) {
alert("Clipboard not supported. Site must be served via https on iOS.");
return;
}
// copy share link to clipboard
const url = getShareLinkForNode(nodeId);
navigator.clipboard.writeText(url);
// tell user we copied it
alert("Link copied to clipboard!");
};
export function getColorForSnr(snr) {
if(snr >= 0) return "#16a34a"; // good
if(snr < 0) return "#dc2626"; // bad
};
export function getPositionPrecisionInMeters(positionPrecision) {
switch (positionPrecision){
case 2: return 5976446;
case 3: return 2988223;
case 4: return 1494111;
case 5: return 747055;
case 6: return 373527;
case 7: return 186763;
case 8: return 93381;
case 9: return 46690;
case 10: return 23345;
case 11: return 11672; // Android LOW_PRECISION
case 12: return 5836;
case 13: return 2918;
case 14: return 1459;
case 15: return 729;
case 16: return 364; // Android MED_PRECISION
case 17: return 182;
case 18: return 91;
case 19: return 45;
case 20: return 22;
case 21: return 11;
case 22: return 5;
case 23: return 2;
case 24: return 1;
case 32: return 0; // Android HIGH_PRECISION
}
return null;
};
export function getTerrainProfileImage(node1, node2) {
// line colour between nodes
const lineColour = "0000FF"; // blue
// node 1 (left side of image)
const node1MarkerColour = "0000FF"; // blue
const node1Latitude = node1.latitude;
const node1Longitude = node1.longitude;
const node1ElevationMSL = ""; // node1.altitude ?? "";
// node 2 (right side of image)
const node2MarkerColour = "0000FF"; // blue
const node2Latitude = node2.latitude;
const node2Longitude = node2.longitude;
const node2ElevationMSL = ""; // node2.altitude ?? "";
// generate terrain profile image url
return "https://heywhatsthat.com/bin/profile-0904.cgi?" + new URLSearchParams({
src: "meshtastic.liamcottle.net",
axes: 1, // include grid lines and a scale
metric: 1, // show metric units
curvature: 0, // don't include the curvature of the earth in the graphic
width: 500,
height: 200,
pt0: `${node1Latitude},${node1Longitude},${lineColour},${node1ElevationMSL},${node1MarkerColour}`,
pt1: `${node2Latitude},${node2Longitude},${lineColour},${node2ElevationMSL},${node2MarkerColour}`,
}).toString();
};
export function formatPositionPrecision(positionPrecision) {
// get position precision in meters
const positionPrecisionInMeters = getPositionPrecisionInMeters(positionPrecision);
if (positionPrecisionInMeters == null){
return "?";
}
// format kilometers
if (positionPrecisionInMeters > 1000){
const positionPrecisionInKilometers = Math.ceil(positionPrecisionInMeters / 1000);
return `±${positionPrecisionInKilometers}km`;
}
// format meters
return `±${positionPrecisionInMeters}m`;
};
// escape strings for tooltips etc, to prevent html/script injection
// not used in vuejs, as that auto escapes
export function escapeString(string) {
return string.replace(/</g, "&lt;").replace(/>/g, "&gt;");
};
export function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
};
// returns true if the element or one of its parents has the class classname
export function elementOrAnyAncestorHasClass(element, className) {
// check if element contains class
if (element.classList && element.classList.contains(className)) {
return true;
}
// check if parent node has the class
if (element.parentNode) {
return elementOrAnyAncestorHasClass(element.parentNode, className);
}
// couldn't find the class
return false;
};
export function formatUptimeSeconds(secondsToFormat) {
secondsToFormat = Number(secondsToFormat);
const days = Math.floor(secondsToFormat / (3600 * 24));
const hours = Math.floor((secondsToFormat % (3600 * 24)) / 3600);
const minutes = Math.floor((secondsToFormat % 3600) / 60);
const seconds = Math.floor(secondsToFormat % 60);
const daysPlural = days === 1 ? 'day' : 'days';
return `${days} ${daysPlural} ${hours}h ${minutes}m ${seconds}s`;
};
export function celsiusToFahrenheit(celsius) {
return (celsius * 9/5) + 32;
};
/** operates on state */
// find node by id
export function findNodeById(id) {
const node = state.nodes.find((node) => node.node_id.toString() === id.toString());
if (node) return node;
return null;
};
// find node marker by id
export function findNodeMarkerById(id) {
return state.nodeMarkers[id] ?? null;
};
/** operates on config */
export function formatTemperature(celsius) {
switch (temperatureFormat.value) {
case "celsius": {
return `${Number(celsius).toFixed(0)}ºC`;
}
case "fahrenheit": {
const fahrenheit = celsiusToFahrenheit(celsius);
return `${fahrenheit.toFixed(0)}ºF`;
}
}
}
export function getTemperatureUnit() {
switch (temperatureFormat.value) {
case "celsius": return "ºC";
case "fahrenheit": return "ºF";
}
};
export function buildPath(endpoint) {
return `${BASE_PATH}${endpoint}`
};
// determine if node was recently heard uplinking packets to mqtt
export function hasNodeUplinkedToMqttRecently(node) {
const now = moment();
const millisecondsSinceNodeLastUplinkedToMqtt = now.diff(moment(node.mqtt_connection_state_updated_at));
return millisecondsSinceNodeLastUplinkedToMqtt < nodesDisconnectedAge.value * 1000;
}