From 11b1d8745dd0fd3e52cade96f9ff02d7f6277dc3 Mon Sep 17 00:00:00 2001
From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com>
Date: Thu, 17 Jul 2025 01:08:10 -0700
Subject: [PATCH] added missing Settings functionality, moved ntfy setting
---
apps/core-server/modules/chron.ts | 1 -
.../(app)/pilot/_components/dme/useSounds.ts | 18 +--
.../app/_components/MicVolumeIndication.tsx | 11 +-
.../dispatch/app/_components/map/BaseMaps.tsx | 1 -
.../app/_components/navbar/Settings.tsx | 119 +++++++++++++-----
.../app/_helpers/liveKitEventHandler.ts | 20 ++-
apps/dispatch/app/_store/audioStore.ts | 37 ++++--
.../app/(app)/settings/_components/forms.tsx | 79 +-----------
apps/hub/app/(app)/settings/page.tsx | 5 +-
9 files changed, 141 insertions(+), 150 deletions(-)
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
{
- setSelectedDevice(e.target.value);
+ setSettingsPartial({ micDeviceId: e.target.value });
setShowIndication(true);
}}
>
@@ -115,10 +133,10 @@ export const SettingsBtn = () => {
step={0.01}
onChange={(e) => {
const value = parseFloat(e.target.value);
- setMicVol(value);
+ setSettingsPartial({ micVolume: value });
setShowIndication(true);
}}
- value={micVol}
+ value={settings.micVolume}
className="range range-xs range-accent w-full"
/>
@@ -130,7 +148,10 @@ export const SettingsBtn = () => {
{showIndication && (
-
+
)}
@@ -144,10 +165,11 @@ export const SettingsBtn = () => {
max={1}
step={0.01}
onChange={(e) => {
+ console.log("Radio Volume", e.target.value);
const value = parseFloat(e.target.value);
- setFunkVol(value);
+ setSettingsPartial({ radioVolume: value });
}}
- value={funkVolume}
+ value={settings.radioVolume}
className="range range-xs range-primary w-full"
/>
@@ -158,9 +180,7 @@ export const SettingsBtn = () => {
100%
-
+
Melder Lautstärke
@@ -172,12 +192,12 @@ export const SettingsBtn = () => {
step={0.01}
onChange={(e) => {
const value = parseFloat(e.target.value);
- setDmeVol(value);
+ setSettingsPartial({ dmeVolume: value });
if (!testSoundRef.current) return;
testSoundRef.current.volume = value;
testSoundRef.current.play();
}}
- value={dmeVolume}
+ value={settings.dmeVolume}
className="range range-xs range-primary w-full"
/>
@@ -188,6 +208,34 @@ export const SettingsBtn = () => {
100%
+
+
+
+
+ NTFY room
+
+ setSettingsPartial({ pilotNtfyRoom: e.target.value })}
+ />
+
+
+
+
+ Hier
+
+ findest du mehr Informationen!
+
+
{
await editUserMutation.mutateAsync({
id: session.data!.user.id,
user: {
- settingsMicDevice: selectedDevice,
- settingsMicVolume: micVol,
- settingsRadioVolume: funkVolume,
- settingsDmeVolume: dmeVolume,
+ settingsMicDevice: settings.micDeviceId,
+ settingsMicVolume: settings.micVolume,
+ settingsRadioVolume: settings.radioVolume,
+ settingsDmeVolume: settings.dmeVolume,
+ settingsNtfyRoom: settings.pilotNtfyRoom,
},
});
- setMic(selectedDevice, micVol);
+ setAudioSettings({
+ micDeviceId: settings.micDeviceId,
+ micVolume: settings.micVolume,
+ radioVolume: settings.radioVolume,
+ dmeVolume: settings.dmeVolume,
+ });
modalRef.current?.close();
toast.success("Einstellungen gespeichert");
}}
@@ -229,7 +283,6 @@ export const SettingsBtn = () => {
);
};
-
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 (
-
- );
-};
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() {
-