218 lines
5.8 KiB
TypeScript
218 lines
5.8 KiB
TypeScript
import switchSound from "../assets/sounds/ui/switch.mp3";
|
||
import popup from "../assets/sounds/ui/popup_open.mp3";
|
||
import notification from "../assets/sounds/ui/notification.mp3";
|
||
import beep from "../assets/sounds/ui/Beep.wav";
|
||
import warning from "../assets/sounds/ui/Warning.wav";
|
||
import ding from "../assets/sounds/ui/Ding.wav";
|
||
import shutter from "../assets/sounds/ui/shutter.mp3";
|
||
import attention from "../assets/sounds/ui/Attention.wav";
|
||
import type { HotlistMatches, SightingType } from "../types/types";
|
||
|
||
export function getSoundFileURL(name: string) {
|
||
const sounds: Record<string, string> = {
|
||
switch: switchSound,
|
||
popup: popup,
|
||
notification: notification,
|
||
beep: beep,
|
||
warning: warning,
|
||
ding: ding,
|
||
shutter: shutter,
|
||
attention: attention,
|
||
};
|
||
return sounds[name] ?? null;
|
||
}
|
||
|
||
export const showSoundURL = (url: URL | string | undefined) => {
|
||
console.log(url);
|
||
};
|
||
|
||
const randomChars = () => {
|
||
const uppercaseAsciiStart = 65;
|
||
const letterIndex = Math.floor(Math.random() * 26);
|
||
const letter = String.fromCharCode(uppercaseAsciiStart + letterIndex);
|
||
|
||
return letter;
|
||
};
|
||
|
||
export function cleanArray(str: string) {
|
||
const toArr = str?.split(",");
|
||
|
||
const cleaned = toArr?.map((el: string) => {
|
||
const test = el.replace(/[^0-9a-z]/gi, "");
|
||
|
||
return test;
|
||
});
|
||
return cleaned;
|
||
}
|
||
|
||
export function parseRTSPUrl(url: string) {
|
||
const regex = /rtsp:\/\/([^:]+):([^@]+)@([^:/]+):?(\d+)?(\/.*)?/;
|
||
const match = url?.match(regex);
|
||
|
||
if (!match) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
username: match[1],
|
||
password: match[2],
|
||
ip: match[3],
|
||
port: match[4] || "554", // default RTSP port
|
||
path: match[5] || "/",
|
||
};
|
||
}
|
||
|
||
const generateNumberPlate = () => {
|
||
const numberPlateLetters = new Array(4);
|
||
const characters: string[] = [];
|
||
for (let index = 0; index <= numberPlateLetters.length; index++) {
|
||
const letter = randomChars();
|
||
characters.push(letter);
|
||
}
|
||
const number = Math.floor(Math.random() * 99);
|
||
characters.splice(2, 0, number + " ");
|
||
const numberPlate = characters.join("");
|
||
|
||
return numberPlate;
|
||
};
|
||
|
||
export const generateNumberPlateList = (numberofSightings = 5) => {
|
||
const numberPlates = [];
|
||
|
||
for (let i = 0; i <= numberofSightings; i++) {
|
||
numberPlates.push(generateNumberPlate());
|
||
}
|
||
|
||
return numberPlates;
|
||
};
|
||
|
||
export const formatNumberPlate = (plate: string) => {
|
||
const splittedPlate = plate?.split("");
|
||
splittedPlate?.splice(4, 0, " ");
|
||
const formattedPlate = splittedPlate?.join("");
|
||
return formattedPlate;
|
||
};
|
||
|
||
export const BLANK_IMG = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||
|
||
export function capitalize(s?: string) {
|
||
return s ? s.charAt(0).toUpperCase() + s.slice(1) : "";
|
||
}
|
||
|
||
export function formatAge(tsMillis: number) {
|
||
const ms = Date.now() - tsMillis;
|
||
const seconds = 5 * Math.floor(ms / 5000); // quantize to 5s like the original
|
||
const d = Math.floor(seconds / (3600 * 24));
|
||
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
||
const m = Math.floor((seconds % 3600) / 60);
|
||
const s = Math.floor(seconds % 60);
|
||
if (d > 0) return `${d}d ago`;
|
||
if (h > 0) return `${h}h ago`;
|
||
if (m > 0) return `${m}m ago`;
|
||
return `${s}s ago`;
|
||
}
|
||
|
||
export function drawRects(
|
||
canvas: HTMLCanvasElement,
|
||
imageEl: HTMLImageElement,
|
||
rects: [number, number, number, number][],
|
||
color: string
|
||
) {
|
||
const ctx = canvas.getContext("2d");
|
||
if (!ctx) return;
|
||
// Ensure canvas size matches displayed image size
|
||
const w = imageEl.clientWidth || imageEl.naturalWidth;
|
||
const h = imageEl.clientHeight || imageEl.naturalHeight;
|
||
if (canvas.width !== w) canvas.width = w;
|
||
if (canvas.height !== h) canvas.height = h;
|
||
|
||
ctx.imageSmoothingEnabled = false;
|
||
ctx.lineWidth = 1;
|
||
ctx.strokeStyle = color;
|
||
|
||
rects.forEach((r) => {
|
||
const [x, y, rw, rh] = r;
|
||
ctx.beginPath();
|
||
ctx.rect(Math.round(x * w), Math.round(y * h), Math.round(rw * w), Math.round(rh * h));
|
||
ctx.stroke();
|
||
});
|
||
}
|
||
|
||
export const checkIsHotListHit = (sigthing: SightingType | null) => {
|
||
if (!sigthing) return;
|
||
if (sigthing?.metadata?.hotlistMatches) {
|
||
const isHotListHit = Object.values(sigthing?.metadata?.hotlistMatches).includes(true);
|
||
|
||
return isHotListHit;
|
||
}
|
||
};
|
||
|
||
export function getHotlistName(obj: HotlistMatches | undefined) {
|
||
if (!obj) return;
|
||
|
||
const hotlistNames = Object.entries(obj)
|
||
.filter(([, value]) => value === true)
|
||
.map(([key]) => key);
|
||
return hotlistNames;
|
||
}
|
||
|
||
export const getNPEDCategory = (r?: SightingType | null) =>
|
||
r?.metadata?.npedJSON?.["NPED CATEGORY"] as "A" | "B" | "C" | "D" | undefined;
|
||
|
||
export const getNPEDReason = (r?: SightingType | null) => r?.metadata?.npedJSON;
|
||
|
||
export const zoomMapping = (zoomLevel: number | undefined) => {
|
||
switch (zoomLevel) {
|
||
case 1:
|
||
return "Near";
|
||
case 2:
|
||
return "Mid";
|
||
case 4:
|
||
return "Far";
|
||
default:
|
||
break;
|
||
}
|
||
};
|
||
|
||
export const reverseZoomMapping = (magnification: string) => {
|
||
switch (magnification) {
|
||
case "near":
|
||
return 1;
|
||
case "mid":
|
||
return 2;
|
||
case "far":
|
||
return 4;
|
||
default:
|
||
break;
|
||
}
|
||
};
|
||
|
||
export const ValidateIPaddress = (value: string | undefined) => {
|
||
if (!value) return;
|
||
|
||
const regex =
|
||
/^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
|
||
|
||
if (!regex.test(value)) {
|
||
return "Invalid IP address format";
|
||
}
|
||
};
|
||
|
||
export function isUtcOutOfSync(
|
||
data: {
|
||
utctime: number;
|
||
localdate: string;
|
||
localtime: string;
|
||
},
|
||
maxDriftMs = 60_000
|
||
) {
|
||
const utc = new Date(data.utctime);
|
||
|
||
const [d, m, y] = data.localdate.split("/").map(Number);
|
||
const [hh, mm, ss] = data.localtime.split(":").map(Number);
|
||
// Construct local Date in device’s local timezone
|
||
const local = new Date(y, m - 1, d, hh, mm, ss);
|
||
const driftMs = Math.abs(utc.getTime() - local.getTime() + utc.getTimezoneOffset() * 60_000);
|
||
return { driftMs, outOfSync: driftMs > maxDriftMs };
|
||
}
|