Merge branch 'develop' into bugfix/uploadsounds-2

This commit is contained in:
2025-10-27 14:28:56 +00:00
5 changed files with 63 additions and 31 deletions

View File

@@ -32,7 +32,8 @@
"react-tabs": "^6.1.0", "react-tabs": "^6.1.0",
"react-use": "^17.6.0", "react-use": "^17.6.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwindcss": "^4.1.11" "tailwindcss": "^4.1.11",
"use-debounce": "^10.0.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.32.0", "@eslint/js": "^9.32.0",

View File

@@ -181,8 +181,8 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
if (!isSightingModalOpen && modalQueue.length > 0) { if (!isSightingModalOpen && modalQueue.length > 0) {
const next = modalQueue[0]; const next = modalQueue[0];
if (next.kind === "NPED") npedSound(); // if (next.kind === "NPED") npedSound();
else hotlistsound(); // else hotlistsound();
setSelectedSighting(next.sighting); setSelectedSighting(next.sighting);
setSightingModalOpen(true); setSightingModalOpen(true);

View File

@@ -1,10 +1,13 @@
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { Query, useQuery } from "@tanstack/react-query"; import { Query, useQuery } from "@tanstack/react-query";
import type { SightingType } from "../types/types"; import type { SightingType } from "../types/types";
import { useSoundOnChange } from "react-sounds"; import { useSound } from "react-sounds";
import { useSoundContext } from "../context/SoundContext"; import { useSoundContext } from "../context/SoundContext";
import { getSoundFileURL } from "../utils/utils"; import { checkIsHotListHit, getNPEDCategory, getSoundFileURL } from "../utils/utils";
import switchSound from "../assets/sounds/ui/switch.mp3"; import switchSound from "../assets/sounds/ui/switch.mp3";
import notification from "../assets/sounds/ui/notification.mp3";
import popup from "../assets/sounds/ui/popup_open.mp3";
async function fetchSighting(url: string | undefined, ref: number): Promise<SightingType> { async function fetchSighting(url: string | undefined, ref: number): Promise<SightingType> {
const res = await fetch(`${url}${ref}`, { const res = await fetch(`${url}${ref}`, {
@@ -21,26 +24,21 @@ export function useSightingFeed(url: string | undefined) {
const [sessionStarted, setSessionStarted] = useState(false); const [sessionStarted, setSessionStarted] = useState(false);
const [selectedSighting, setSelectedSighting] = useState<SightingType | null>(null); const [selectedSighting, setSelectedSighting] = useState<SightingType | null>(null);
const mostRecent = sightings[0] ?? null; const soundSrcHotlist = useMemo(() => {
const latestRef = mostRecent?.ref ?? null; if (state?.hotlistSound?.includes(".mp3") || state.hotlistSound?.includes(".wav")) {
const file = state.soundOptions?.find((item) => item.name === state.hotlistSound);
const first = useRef(true); return file?.soundUrl ?? notification;
const lastSoundAt = useRef(0);
const COOLDOWN_MS = 1500;
const currentRef = useRef<number>(-1);
const lastValidTimestamp = useRef<number>(Date.now());
const trigger = useMemo(() => {
if (latestRef == null || !audioArmed) return null;
if (first.current) {
first.current = false;
return Symbol("skip");
} }
const now = Date.now(); return getSoundFileURL(state?.hotlistSound) ?? notification;
if (now - lastSoundAt.current < COOLDOWN_MS) return Symbol("skip"); }, [state.hotlistSound, state.soundOptions]);
lastSoundAt.current = now;
return latestRef; const soundSrcNped = useMemo(() => {
}, [audioArmed, latestRef]); if (state?.NPEDsound?.includes(".mp3") || state.NPEDsound?.includes(".wav")) {
const file = state.soundOptions?.find((item) => item.name === state.NPEDsound);
return file?.soundUrl ?? popup;
}
return getSoundFileURL(state.NPEDsound) ?? popup;
}, [state.NPEDsound, state.soundOptions]);
const soundSrc = useMemo(() => { const soundSrc = useMemo(() => {
if (state?.sightingSound?.includes(".mp3") || state.sightingSound?.includes(".wav")) { if (state?.sightingSound?.includes(".mp3") || state.sightingSound?.includes(".wav")) {
@@ -50,6 +48,15 @@ export function useSightingFeed(url: string | undefined) {
return getSoundFileURL(state?.sightingSound) ?? switchSound; return getSoundFileURL(state?.sightingSound) ?? switchSound;
}, [state.sightingSound, state.soundOptions]); }, [state.sightingSound, state.soundOptions]);
const { play: hotlistsound } = useSound(soundSrcHotlist, { volume: state.hotlistSoundVolume });
const { play: npedSound } = useSound(soundSrcNped, { volume: state.NPEDsoundVolume });
const { play: sightingSound } = useSound(soundSrc, { volume: state.sightingVolume });
const mostRecent = sightings[0] ?? null;
const currentRef = useRef<number>(-1);
const lastValidTimestamp = useRef<number>(Date.now());
function refetchInterval(query: Query<SightingType, Error, SightingType, (string | undefined)[]>) { function refetchInterval(query: Query<SightingType, Error, SightingType, (string | undefined)[]>) {
if (!query) return; if (!query) return;
const data = query.state.data as SightingType | undefined; const data = query.state.data as SightingType | undefined;
@@ -78,16 +85,36 @@ export function useSightingFeed(url: string | undefined) {
staleTime: 0, staleTime: 0,
}); });
//use latestref instead of trigger to revert back const playHotlistsound = useDebouncedCallback(() => {
hotlistsound();
}, 500);
useSoundOnChange(soundSrc, trigger, { const playNPEDHitSound = useDebouncedCallback(() => {
volume: state.sightingVolume, npedSound();
initial: false, }, 500);
});
const playSightingHitSound = useDebouncedCallback(() => {
sightingSound();
}, 500);
useEffect(() => { useEffect(() => {
const data = query.data; const data = query.data;
if (!data || data.ref === -1) return; if (!data || data.ref === -1) return;
const isHotListHit = checkIsHotListHit(data);
const cat = getNPEDCategory(data);
const isNPEDHitA = cat === "A";
const isNPEDHitB = cat === "B";
const isNPEDHitC = cat === "C";
if ((isNPEDHitA && audioArmed) || (isNPEDHitB && audioArmed) || (isNPEDHitC && audioArmed)) {
playNPEDHitSound();
} else if (isHotListHit && audioArmed) {
playHotlistsound();
} else if (audioArmed) {
playSightingHitSound();
}
const now = Date.now(); const now = Date.now();

View File

@@ -6,7 +6,6 @@ import warning from "../assets/sounds/ui/Warning.wav";
import ding from "../assets/sounds/ui/Ding.wav"; import ding from "../assets/sounds/ui/Ding.wav";
import shutter from "../assets/sounds/ui/shutter.mp3"; import shutter from "../assets/sounds/ui/shutter.mp3";
import attention from "../assets/sounds/ui/Attention.wav"; import attention from "../assets/sounds/ui/Attention.wav";
import type { HotlistMatches, SightingType } from "../types/types"; import type { HotlistMatches, SightingType } from "../types/types";
export function getSoundFileURL(name: string) { export function getSoundFileURL(name: string) {
@@ -158,4 +157,4 @@ export function getHotlistName(obj: HotlistMatches | undefined) {
} }
export const getNPEDCategory = (r?: SightingType | null) => export const getNPEDCategory = (r?: SightingType | null) =>
r?.metadata?.npedJSON?.["NPED CATEGORY"] as "A" | "B" | "C" | undefined; r?.metadata?.npedJSON?.["NPED CATEGORY"] as "A" | "B" | "C" | "D" | undefined;

View File

@@ -2267,6 +2267,11 @@ uri-js@^4.2.2:
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
use-debounce@^10.0.6:
version "10.0.6"
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.6.tgz#e05060a5e561432ec740c653698f3eb162bd28ec"
integrity sha512-C5OtPyhAZgVoteO9heXMTdW7v/IbFI+8bSVKYCJrSmiWWCLsbUxiBSp4t9v0hNBTGY97bT72ydDIDyGSFWfwXg==
vite@^7.1.0: vite@^7.1.0:
version "7.1.2" version "7.1.2"
resolved "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz" resolved "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz"