2025-09-30 15:32:00 +01:00
|
|
|
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";
|
|
|
|
|
|
2025-10-01 15:21:07 +01:00
|
|
|
export function getSoundFileURL(name: string) {
|
2025-09-30 15:32:00 +01:00
|
|
|
const sounds: Record<string, string> = {
|
|
|
|
|
switch: switchSound,
|
|
|
|
|
popup: popup,
|
|
|
|
|
notification: notification,
|
|
|
|
|
};
|
|
|
|
|
return sounds[name] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 14:23:48 +01:00
|
|
|
const randomChars = () => {
|
|
|
|
|
const uppercaseAsciiStart = 65;
|
|
|
|
|
const letterIndex = Math.floor(Math.random() * 26);
|
|
|
|
|
const letter = String.fromCharCode(uppercaseAsciiStart + letterIndex);
|
|
|
|
|
|
|
|
|
|
return letter;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-03 13:08:21 +01:00
|
|
|
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] || "/",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 14:23:48 +01:00
|
|
|
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;
|
|
|
|
|
};
|
2025-08-20 08:27:05 +01:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-22 10:38:28 +01:00
|
|
|
|
|
|
|
|
// setSelectedRef(data?.ref);
|
|
|
|
|
|
|
|
|
|
//setItems(data);
|
|
|
|
|
|
|
|
|
|
// const selected = useMemo(
|
|
|
|
|
// () =>
|
|
|
|
|
// selectedRef == null
|
|
|
|
|
// ? null
|
|
|
|
|
// : items.find((x) => x?.ref === selectedRef) ?? null,
|
|
|
|
|
// [items, selectedRef]
|
|
|
|
|
// );
|
|
|
|
|
// const effectiveSelected = selected ?? mostRecent ?? null;
|
|
|
|
|
|
|
|
|
|
// useEffect(() => {
|
|
|
|
|
// let delay = pollMs;
|
|
|
|
|
// let dead = false;
|
|
|
|
|
// const controller = new AbortController();
|
|
|
|
|
|
|
|
|
|
// async function tick() {
|
|
|
|
|
// try {
|
|
|
|
|
// // Pause when tab hidden to save CPU/network
|
|
|
|
|
// if (document.hidden) {
|
|
|
|
|
// setTimeout(tick, Math.max(delay, 2000));
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if (obj && typeof obj.ref === "number" && obj.ref > -1) {
|
|
|
|
|
// setItems((prev) => {
|
|
|
|
|
// const next = [obj, ...prev].slice(0, limit);
|
|
|
|
|
// // maintain selection if still present; otherwise select newest if allowed
|
|
|
|
|
// const stillExists =
|
|
|
|
|
// selectedRef != null && next.some((x) => x?.ref === selectedRef);
|
|
|
|
|
// if (autoSelectLatest && !stillExists) {
|
|
|
|
|
// setSelectedRef(obj.ref);
|
|
|
|
|
// }
|
|
|
|
|
// return next;
|
|
|
|
|
// });
|
|
|
|
|
// setMostRecent(obj);
|
|
|
|
|
// mostRecentRef.current = obj.ref;
|
|
|
|
|
// delay = pollMs; // reset backoff on success
|
|
|
|
|
// }
|
|
|
|
|
// } catch {
|
|
|
|
|
// // exponential backoff (max 10s)
|
|
|
|
|
// delay = Math.min(delay * 2, 10000);
|
|
|
|
|
// } finally {
|
|
|
|
|
// if (!dead) setTimeout(tick, delay);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const t = setTimeout(tick, pollMs);
|
|
|
|
|
// return () => {
|
|
|
|
|
// dead = true;
|
|
|
|
|
// controller.abort();
|
|
|
|
|
// clearTimeout(t);
|
|
|
|
|
// };
|
|
|
|
|
// }, [baseUrl, limit, pollMs, autoSelectLatest, selectedRef]);
|