diff --git a/apps/core-server/modules/chron.ts b/apps/core-server/modules/chron.ts index d2870245..7f8764db 100644 --- a/apps/core-server/modules/chron.ts +++ b/apps/core-server/modules/chron.ts @@ -153,7 +153,6 @@ const removeConnectedAircrafts = async () => { cron.schedule("*/1 * * * *", async () => { try { - console.log("Running cron job to remove closed missions and connected aircrafts..."); await removeClosedMissions(); await removeConnectedAircrafts(); } catch (error) { diff --git a/apps/dispatch/app/(app)/pilot/_components/dme/useSounds.ts b/apps/dispatch/app/(app)/pilot/_components/dme/useSounds.ts index 64a1ff4d..188536b3 100644 --- a/apps/dispatch/app/(app)/pilot/_components/dme/useSounds.ts +++ b/apps/dispatch/app/(app)/pilot/_components/dme/useSounds.ts @@ -1,18 +1,11 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; -import { getUserAPI } from "_querys/user"; +import { useAudioStore } from "_store/audioStore"; import { usePilotConnectionStore } from "_store/pilot/connectionStore"; import { useDmeStore } from "_store/pilot/dmeStore"; -import { useSession } from "next-auth/react"; import { useEffect, useRef } from "react"; export const useSounds = () => { - const session = useSession(); - const { data: user } = useQuery({ - queryKey: ["user", session.data?.user.id], - queryFn: () => getUserAPI(session.data!.user.id), - }); - + const dmeVolume = useAudioStore((state) => state.settings.dmeVolume); const { page, setPage } = useDmeStore((state) => state); const mission = usePilotConnectionStore((state) => state.activeMission); @@ -25,14 +18,14 @@ export const useSounds = () => { }, []); useEffect(() => { - if (user?.settingsDmeVolume) { + if (dmeVolume) { if (newMissionSound.current) { - newMissionSound.current.volume = user.settingsDmeVolume; + newMissionSound.current.volume = dmeVolume; } } else if (newMissionSound.current) { newMissionSound.current.volume = 0.8; // Default volume } - }, [user?.settingsDmeVolume]); + }, [dmeVolume]); useEffect(() => { const timeouts: NodeJS.Timeout[] = []; @@ -40,7 +33,6 @@ export const useSounds = () => { if (page === "new-mission" && newMissionSound.current) { console.log("new-mission", mission); newMissionSound.current.currentTime = 0; - newMissionSound.current.volume = 0.3; newMissionSound.current.play(); if (mission) { timeouts.push(setTimeout(() => setPage({ page: "mission", mission }), 500)); diff --git a/apps/dispatch/app/_components/MicVolumeIndication.tsx b/apps/dispatch/app/_components/MicVolumeIndication.tsx index 3e80b148..97f39ae6 100644 --- a/apps/dispatch/app/_components/MicVolumeIndication.tsx +++ b/apps/dispatch/app/_components/MicVolumeIndication.tsx @@ -47,27 +47,28 @@ export default function MicrophoneLevel({ deviceId, volumeInput }: MicrophoneLev }; }, [deviceId, volumeInput]); - const barWidth = Math.max((volumeLevel / 70) * 100 - 35, 0); + const barWidth = Math.min((volumeLevel / 140) * 100, 100); return (
100 && "bg-red-400")} + className={cn("bg-primary h-full rounded", barWidth == 100 && "bg-red-400")} style={{ - width: `${barWidth > 100 ? 100 : barWidth}%`, + width: `${barWidth}%`, transition: "width 0.2s", }} />

- Lautstärke sollte beim Sprechen in dem Grünen bereich bleiben + Lautstärke sollte beim Sprechen in dem Grünen bereich bleiben. Beachte das scharfe Laute + (z.B. "S" oder "Z") die Anzeige verfälschen können.

); diff --git a/apps/dispatch/app/_components/map/BaseMaps.tsx b/apps/dispatch/app/_components/map/BaseMaps.tsx index 5e3fdfbc..6648ea4a 100644 --- a/apps/dispatch/app/_components/map/BaseMaps.tsx +++ b/apps/dispatch/app/_components/map/BaseMaps.tsx @@ -73,7 +73,6 @@ const HeliportsLayer = () => { queryKey: ["heliports"], queryFn: () => getHeliportsAPI(), }); - console.log("Heliports Layer", heliports); const [heliportsWithIcon, setHeliportsWithIcon] = useState<(Heliport & { icon?: string })[]>([]); const map = useMap(); const [isVisible, setIsVisible] = useState(true); diff --git a/apps/dispatch/app/_components/navbar/Settings.tsx b/apps/dispatch/app/_components/navbar/Settings.tsx index 72ff3fe9..eeddeed6 100644 --- a/apps/dispatch/app/_components/navbar/Settings.tsx +++ b/apps/dispatch/app/_components/navbar/Settings.tsx @@ -1,17 +1,19 @@ "use client"; import { useEffect, useRef, useState } from "react"; import { GearIcon } from "@radix-ui/react-icons"; -import { SettingsIcon, Volume2 } from "lucide-react"; +import { Bell, SettingsIcon, Volume2 } from "lucide-react"; import MicVolumeBar from "_components/MicVolumeIndication"; import { useMutation, useQuery } from "@tanstack/react-query"; import { editUserAPI, getUserAPI } from "_querys/user"; import { useSession } from "next-auth/react"; import { useAudioStore } from "_store/audioStore"; import toast from "react-hot-toast"; +import Link from "next/link"; export const SettingsBtn = () => { const session = useSession(); + const [inputDevices, setInputDevices] = useState([]); const { data: user } = useQuery({ queryKey: ["user", session.data?.user.id], queryFn: () => getUserAPI(session.data!.user.id), @@ -30,26 +32,42 @@ export const SettingsBtn = () => { const modalRef = useRef(null); - const [inputDevices, setInputDevices] = useState([]); - const [selectedDevice, setSelectedDevice] = useState( - user?.settingsMicDevice || null, - ); const [showIndication, setShowIndication] = useState(false); - const [micVol, setMicVol] = useState(1); - const [funkVolume, setFunkVol] = useState(0.8); - const [dmeVolume, setDmeVol] = useState(0.8); - const { setMic } = useAudioStore((state) => state); + const [settings, setSettings] = useState({ + micDeviceId: user?.settingsMicDevice || null, + micVolume: user?.settingsMicVolume || 1, + radioVolume: user?.settingsRadioVolume || 0.8, + dmeVolume: user?.settingsDmeVolume || 0.8, + pilotNtfyRoom: user?.settingsNtfyRoom || "", + }); + + const { setSettings: setAudioSettings } = useAudioStore((state) => state); useEffect(() => { if (user) { - setSelectedDevice(user.settingsMicDevice); - setMic(user.settingsMicDevice, user.settingsMicVolume || 1); - setMicVol(user.settingsMicVolume || 1); - setFunkVol(user.settingsRadioVolume || 0.8); - setDmeVol(user.settingsDmeVolume || 0.8); + setAudioSettings({ + micDeviceId: user.settingsMicDevice, + micVolume: user.settingsMicVolume || 1, + radioVolume: user.settingsRadioVolume || 0.8, + dmeVolume: user.settingsDmeVolume || 0.8, + }); + setSettings({ + micDeviceId: user.settingsMicDevice, + micVolume: user.settingsMicVolume || 1, + radioVolume: user.settingsRadioVolume || 0.8, + dmeVolume: user.settingsDmeVolume || 0.8, + pilotNtfyRoom: user.settingsNtfyRoom || "", + }); } - }, [user, setMic]); + }, [user, setSettings, setAudioSettings]); + + const setSettingsPartial = (newSettings: Partial) => { + setSettings((prev) => ({ + ...prev, + ...newSettings, + })); + }; useEffect(() => { const setDevices = async () => { @@ -87,9 +105,9 @@ export const SettingsBtn = () => { Eingabegerät setSettingsPartial({ pilotNtfyRoom: e.target.value })} + /> + + +

+ + Hier + + findest du mehr Informationen! +

+
); }; - export const Settings = () => { return (
diff --git a/apps/dispatch/app/_helpers/liveKitEventHandler.ts b/apps/dispatch/app/_helpers/liveKitEventHandler.ts index 3d93f718..83374ce6 100644 --- a/apps/dispatch/app/_helpers/liveKitEventHandler.ts +++ b/apps/dispatch/app/_helpers/liveKitEventHandler.ts @@ -15,17 +15,25 @@ export const handleTrackSubscribed = ( if (!track.isMuted) { useAudioStore.getState().addSpeakingParticipant(participant); } - track.on("unmuted", () => { - useAudioStore.getState().addSpeakingParticipant(participant); - }); - track.on("muted", () => { - useAudioStore.getState().removeSpeakingParticipant(participant); - }); + if (track.kind === Track.Kind.Video || track.kind === Track.Kind.Audio) { // attach it to a new HTMLVideoElement or HTMLAudioElement const element = track.attach(); element.play(); + + track.on("unmuted", () => { + useAudioStore.getState().addSpeakingParticipant(participant); + console.log(useAudioStore.getState().settings.radioVolume); + element.volume = useAudioStore.getState().settings.radioVolume; + }); + track.on("unmuted", () => { + useAudioStore.getState().addSpeakingParticipant(participant); + }); } + + track.on("muted", () => { + useAudioStore.getState().removeSpeakingParticipant(participant); + }); }; export const handleTrackUnsubscribed = (track: RemoteTrack) => { diff --git a/apps/dispatch/app/_store/audioStore.ts b/apps/dispatch/app/_store/audioStore.ts index fc4e5a6c..f5b36310 100644 --- a/apps/dispatch/app/_store/audioStore.ts +++ b/apps/dispatch/app/_store/audioStore.ts @@ -24,8 +24,12 @@ import { getRadioStream } from "_helpers/radioEffect"; let interval: NodeJS.Timeout; type TalkState = { - micDeviceId: string | null; - micVolume: number; + settings: { + micDeviceId: string | null; + micVolume: number; + radioVolume: number; + dmeVolume: number; + }; isTalking: boolean; transmitBlocked: boolean; removeMessage: () => void; @@ -34,7 +38,7 @@ type TalkState = { connectionQuality: ConnectionQuality; remoteParticipants: number; toggleTalking: () => void; - setMic: (micDeviceId: string | null, volume: number) => void; + setSettings: (settings: Partial) => void; connect: (roomName: string, role: string) => void; disconnect: () => void; speakingParticipants: Participant[]; @@ -54,9 +58,14 @@ export const useAudioStore = create((set, get) => ({ localRadioTrack: undefined, transmitBlocked: false, message: null, - micDeviceId: null, speakingParticipants: [], micVolume: 1, + settings: { + micDeviceId: null, + micVolume: 1, + radioVolume: 0.8, + dmeVolume: 0.8, + }, state: "disconnected" as const, remoteParticipants: 0, connectionQuality: ConnectionQuality.Unknown, @@ -84,9 +93,19 @@ export const useAudioStore = create((set, get) => ({ set({ transmitBlocked: false, message: null, isTalking: true }); } }, - setMic: (micDeviceId, micVolume) => { - set({ micDeviceId, micVolume }); - if (get().state === "connected") { + setSettings: (newSettings) => { + const oldSettings = get().settings; + set((s) => ({ + settings: { + ...s.settings, + ...newSettings, + }, + })); + if ( + get().state === "connected" && + (oldSettings.micDeviceId !== newSettings.micDeviceId || + oldSettings.micVolume !== newSettings.micVolume) + ) { const { room, disconnect, connect } = get(); const role = room?.localParticipant.attributes.role; console.log(role); @@ -150,13 +169,13 @@ export const useAudioStore = create((set, get) => ({ const inputStream = await navigator.mediaDevices.getUserMedia({ audio: { - deviceId: get().micDeviceId ?? undefined, + deviceId: get().settings.micDeviceId ?? undefined, noiseSuppression: true, }, }); // Funk-Effekt anwenden - const radioStream = getRadioStream(inputStream, get().micVolume); + const radioStream = getRadioStream(inputStream, get().settings.micVolume); if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen"); const [track] = radioStream.getAudioTracks(); diff --git a/apps/hub/app/(app)/settings/_components/forms.tsx b/apps/hub/app/(app)/settings/_components/forms.tsx index 0938ad25..940baa12 100644 --- a/apps/hub/app/(app)/settings/_components/forms.tsx +++ b/apps/hub/app/(app)/settings/_components/forms.tsx @@ -20,9 +20,7 @@ import { LockOpen1Icon, } from "@radix-ui/react-icons"; import toast from "react-hot-toast"; -import { UserOptionalDefaults, UserOptionalDefaultsSchema } from "@repo/db/zod"; -import { Bell, CircleAlert, Plane, Trash2 } from "lucide-react"; -import Link from "next/link"; +import { CircleAlert, Trash2 } from "lucide-react"; import { deleteUser, sendVerificationLink } from "(app)/admin/user/action"; import { setStandardName } from "../../../../helper/discord"; @@ -458,78 +456,3 @@ export const PasswordForm = (): React.JSX.Element => { ); }; - -export const PilotForm = ({ user }: { user: User }): React.JSX.Element | null => { - const [isLoading, setIsLoading] = useState(false); - - const form = useForm({ - defaultValues: { - ...user, - emailVerified: user.emailVerified ?? undefined, - }, - resolver: zodResolver(UserOptionalDefaultsSchema), - }); - - if (!user) return null; - return ( -
{ - form.handleSubmit(async () => { - setIsLoading(true); - }); - await updateUser(values); - setIsLoading(false); - form.reset(values); - toast.success("Deine Änderungen wurden gespeichert!", { - style: { - background: "var(--color-base-100)", - color: "var(--color-base-content)", - }, - }); - })} - > -

- Pilot -

-
- - -

- - Hier - - findest du mehr Informationen! -

- - {form.formState.errors.settingsNtfyRoom && ( -

{form.formState.errors.settingsNtfyRoom.message}

- )} -
-
- -
-
- ); -}; diff --git a/apps/hub/app/(app)/settings/page.tsx b/apps/hub/app/(app)/settings/page.tsx index ba0139f2..240e60db 100644 --- a/apps/hub/app/(app)/settings/page.tsx +++ b/apps/hub/app/(app)/settings/page.tsx @@ -1,6 +1,6 @@ import { prisma } from "@repo/db"; import { getServerSession } from "../../api/auth/[...nextauth]/auth"; -import { ProfileForm, SocialForm, PasswordForm, PilotForm, DeleteForm } from "./_components/forms"; +import { ProfileForm, SocialForm, PasswordForm, DeleteForm } from "./_components/forms"; import { GearIcon } from "@radix-ui/react-icons"; import { Error } from "_components/Error"; @@ -48,9 +48,6 @@ export default async function Page() {
-
- -