added missing Settings functionality, moved ntfy setting
This commit is contained in:
@@ -153,7 +153,6 @@ const removeConnectedAircrafts = async () => {
|
|||||||
|
|
||||||
cron.schedule("*/1 * * * *", async () => {
|
cron.schedule("*/1 * * * *", async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Running cron job to remove closed missions and connected aircrafts...");
|
|
||||||
await removeClosedMissions();
|
await removeClosedMissions();
|
||||||
await removeConnectedAircrafts();
|
await removeConnectedAircrafts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useAudioStore } from "_store/audioStore";
|
||||||
import { getUserAPI } from "_querys/user";
|
|
||||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
import { useDmeStore } from "_store/pilot/dmeStore";
|
import { useDmeStore } from "_store/pilot/dmeStore";
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
export const useSounds = () => {
|
export const useSounds = () => {
|
||||||
const session = useSession();
|
const dmeVolume = useAudioStore((state) => state.settings.dmeVolume);
|
||||||
const { data: user } = useQuery({
|
|
||||||
queryKey: ["user", session.data?.user.id],
|
|
||||||
queryFn: () => getUserAPI(session.data!.user.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { page, setPage } = useDmeStore((state) => state);
|
const { page, setPage } = useDmeStore((state) => state);
|
||||||
const mission = usePilotConnectionStore((state) => state.activeMission);
|
const mission = usePilotConnectionStore((state) => state.activeMission);
|
||||||
|
|
||||||
@@ -25,14 +18,14 @@ export const useSounds = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.settingsDmeVolume) {
|
if (dmeVolume) {
|
||||||
if (newMissionSound.current) {
|
if (newMissionSound.current) {
|
||||||
newMissionSound.current.volume = user.settingsDmeVolume;
|
newMissionSound.current.volume = dmeVolume;
|
||||||
}
|
}
|
||||||
} else if (newMissionSound.current) {
|
} else if (newMissionSound.current) {
|
||||||
newMissionSound.current.volume = 0.8; // Default volume
|
newMissionSound.current.volume = 0.8; // Default volume
|
||||||
}
|
}
|
||||||
}, [user?.settingsDmeVolume]);
|
}, [dmeVolume]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeouts: NodeJS.Timeout[] = [];
|
const timeouts: NodeJS.Timeout[] = [];
|
||||||
@@ -40,7 +33,6 @@ export const useSounds = () => {
|
|||||||
if (page === "new-mission" && newMissionSound.current) {
|
if (page === "new-mission" && newMissionSound.current) {
|
||||||
console.log("new-mission", mission);
|
console.log("new-mission", mission);
|
||||||
newMissionSound.current.currentTime = 0;
|
newMissionSound.current.currentTime = 0;
|
||||||
newMissionSound.current.volume = 0.3;
|
|
||||||
newMissionSound.current.play();
|
newMissionSound.current.play();
|
||||||
if (mission) {
|
if (mission) {
|
||||||
timeouts.push(setTimeout(() => setPage({ page: "mission", mission }), 500));
|
timeouts.push(setTimeout(() => setPage({ page: "mission", mission }), 500));
|
||||||
|
|||||||
@@ -47,27 +47,28 @@ export default function MicrophoneLevel({ deviceId, volumeInput }: MicrophoneLev
|
|||||||
};
|
};
|
||||||
}, [deviceId, volumeInput]);
|
}, [deviceId, volumeInput]);
|
||||||
|
|
||||||
const barWidth = Math.max((volumeLevel / 70) * 100 - 35, 0);
|
const barWidth = Math.min((volumeLevel / 140) * 100, 100);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="relative w-full bg-base-300 h-5 rounded">
|
<div className="relative w-full bg-base-300 h-5 rounded">
|
||||||
<div
|
<div
|
||||||
className={cn("bg-primary h-full rounded", barWidth > 100 && "bg-red-400")}
|
className={cn("bg-primary h-full rounded", barWidth == 100 && "bg-red-400")}
|
||||||
style={{
|
style={{
|
||||||
width: `${barWidth > 100 ? 100 : barWidth}%`,
|
width: `${barWidth}%`,
|
||||||
transition: "width 0.2s",
|
transition: "width 0.2s",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-[60%] w-[20%] h-full bg-green-500 opacity-40 rounded"
|
className="absolute top-0 left-[60%] w-[30%] h-full bg-green-500 opacity-40 rounded"
|
||||||
style={{
|
style={{
|
||||||
transform: "translateX(-50%)",
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-500 text-sm">
|
<p className="text-gray-500 text-sm">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ const HeliportsLayer = () => {
|
|||||||
queryKey: ["heliports"],
|
queryKey: ["heliports"],
|
||||||
queryFn: () => getHeliportsAPI(),
|
queryFn: () => getHeliportsAPI(),
|
||||||
});
|
});
|
||||||
console.log("Heliports Layer", heliports);
|
|
||||||
const [heliportsWithIcon, setHeliportsWithIcon] = useState<(Heliport & { icon?: string })[]>([]);
|
const [heliportsWithIcon, setHeliportsWithIcon] = useState<(Heliport & { icon?: string })[]>([]);
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const [isVisible, setIsVisible] = useState(true);
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { GearIcon } from "@radix-ui/react-icons";
|
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 MicVolumeBar from "_components/MicVolumeIndication";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { editUserAPI, getUserAPI } from "_querys/user";
|
import { editUserAPI, getUserAPI } from "_querys/user";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useAudioStore } from "_store/audioStore";
|
import { useAudioStore } from "_store/audioStore";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export const SettingsBtn = () => {
|
export const SettingsBtn = () => {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
|
||||||
|
const [inputDevices, setInputDevices] = useState<MediaDeviceInfo[]>([]);
|
||||||
const { data: user } = useQuery({
|
const { data: user } = useQuery({
|
||||||
queryKey: ["user", session.data?.user.id],
|
queryKey: ["user", session.data?.user.id],
|
||||||
queryFn: () => getUserAPI(session.data!.user.id),
|
queryFn: () => getUserAPI(session.data!.user.id),
|
||||||
@@ -30,26 +32,42 @@ export const SettingsBtn = () => {
|
|||||||
|
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
|
||||||
const [inputDevices, setInputDevices] = useState<MediaDeviceInfo[]>([]);
|
|
||||||
const [selectedDevice, setSelectedDevice] = useState<string | null>(
|
|
||||||
user?.settingsMicDevice || null,
|
|
||||||
);
|
|
||||||
const [showIndication, setShowIndication] = useState<boolean>(false);
|
const [showIndication, setShowIndication] = useState<boolean>(false);
|
||||||
const [micVol, setMicVol] = useState<number>(1);
|
|
||||||
const [funkVolume, setFunkVol] = useState<number>(0.8);
|
|
||||||
const [dmeVolume, setDmeVol] = useState<number>(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(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
setSelectedDevice(user.settingsMicDevice);
|
setAudioSettings({
|
||||||
setMic(user.settingsMicDevice, user.settingsMicVolume || 1);
|
micDeviceId: user.settingsMicDevice,
|
||||||
setMicVol(user.settingsMicVolume || 1);
|
micVolume: user.settingsMicVolume || 1,
|
||||||
setFunkVol(user.settingsRadioVolume || 0.8);
|
radioVolume: user.settingsRadioVolume || 0.8,
|
||||||
setDmeVol(user.settingsDmeVolume || 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<typeof settings>) => {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...newSettings,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setDevices = async () => {
|
const setDevices = async () => {
|
||||||
@@ -87,9 +105,9 @@ export const SettingsBtn = () => {
|
|||||||
<span>Eingabegerät</span>
|
<span>Eingabegerät</span>
|
||||||
<select
|
<select
|
||||||
className="input w-full"
|
className="input w-full"
|
||||||
value={selectedDevice ? selectedDevice : ""}
|
value={settings.micDeviceId ? settings.micDeviceId : ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedDevice(e.target.value);
|
setSettingsPartial({ micDeviceId: e.target.value });
|
||||||
setShowIndication(true);
|
setShowIndication(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -115,10 +133,10 @@ export const SettingsBtn = () => {
|
|||||||
step={0.01}
|
step={0.01}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = parseFloat(e.target.value);
|
const value = parseFloat(e.target.value);
|
||||||
setMicVol(value);
|
setSettingsPartial({ micVolume: value });
|
||||||
setShowIndication(true);
|
setShowIndication(true);
|
||||||
}}
|
}}
|
||||||
value={micVol}
|
value={settings.micVolume}
|
||||||
className="range range-xs range-accent w-full"
|
className="range range-xs range-accent w-full"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
||||||
@@ -130,7 +148,10 @@ export const SettingsBtn = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showIndication && (
|
{showIndication && (
|
||||||
<MicVolumeBar deviceId={selectedDevice ? selectedDevice : ""} volumeInput={micVol} />
|
<MicVolumeBar
|
||||||
|
deviceId={settings.micDeviceId ? settings.micDeviceId : ""}
|
||||||
|
volumeInput={settings.micVolume}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="divider w-full" />
|
<div className="divider w-full" />
|
||||||
</div>
|
</div>
|
||||||
@@ -144,10 +165,11 @@ export const SettingsBtn = () => {
|
|||||||
max={1}
|
max={1}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
console.log("Radio Volume", e.target.value);
|
||||||
const value = parseFloat(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"
|
className="range range-xs range-primary w-full"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
||||||
@@ -158,9 +180,7 @@ export const SettingsBtn = () => {
|
|||||||
<span>100%</span>
|
<span>100%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center w-full">
|
|
||||||
<div className="divider w-1/2" />
|
|
||||||
</div>
|
|
||||||
<p className="flex items-center gap-2 text-base mb-2">
|
<p className="flex items-center gap-2 text-base mb-2">
|
||||||
<Volume2 size={20} /> Melder Lautstärke
|
<Volume2 size={20} /> Melder Lautstärke
|
||||||
</p>
|
</p>
|
||||||
@@ -172,12 +192,12 @@ export const SettingsBtn = () => {
|
|||||||
step={0.01}
|
step={0.01}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = parseFloat(e.target.value);
|
const value = parseFloat(e.target.value);
|
||||||
setDmeVol(value);
|
setSettingsPartial({ dmeVolume: value });
|
||||||
if (!testSoundRef.current) return;
|
if (!testSoundRef.current) return;
|
||||||
testSoundRef.current.volume = value;
|
testSoundRef.current.volume = value;
|
||||||
testSoundRef.current.play();
|
testSoundRef.current.play();
|
||||||
}}
|
}}
|
||||||
value={dmeVolume}
|
value={settings.dmeVolume}
|
||||||
className="range range-xs range-primary w-full"
|
className="range range-xs range-primary w-full"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
<div className="flex justify-between px-2.5 mt-2 text-xs">
|
||||||
@@ -188,6 +208,34 @@ export const SettingsBtn = () => {
|
|||||||
<span>100%</span>
|
<span>100%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-center w-full">
|
||||||
|
<div className="divider w-full" />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<label className="floating-label w-full">
|
||||||
|
<span className="text-lg flex items-center gap-2">
|
||||||
|
<Bell /> NTFY room
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
placeholder="Erhalte eine Benachrichtigung auf dein Handy über NTFY"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
value={settings.pilotNtfyRoom}
|
||||||
|
onChange={(e) => setSettingsPartial({ pilotNtfyRoom: e.target.value })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<p className="label mt-2 w-full">
|
||||||
|
<Link
|
||||||
|
href="https://docs.virtualairrescue.com/docs/Leitstelle/App-Alarmierung#download"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="link link-hover link-primary"
|
||||||
|
>
|
||||||
|
Hier
|
||||||
|
</Link>
|
||||||
|
findest du mehr Informationen!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between modal-action">
|
<div className="flex justify-between modal-action">
|
||||||
<button
|
<button
|
||||||
@@ -210,13 +258,19 @@ export const SettingsBtn = () => {
|
|||||||
await editUserMutation.mutateAsync({
|
await editUserMutation.mutateAsync({
|
||||||
id: session.data!.user.id,
|
id: session.data!.user.id,
|
||||||
user: {
|
user: {
|
||||||
settingsMicDevice: selectedDevice,
|
settingsMicDevice: settings.micDeviceId,
|
||||||
settingsMicVolume: micVol,
|
settingsMicVolume: settings.micVolume,
|
||||||
settingsRadioVolume: funkVolume,
|
settingsRadioVolume: settings.radioVolume,
|
||||||
settingsDmeVolume: dmeVolume,
|
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();
|
modalRef.current?.close();
|
||||||
toast.success("Einstellungen gespeichert");
|
toast.success("Einstellungen gespeichert");
|
||||||
}}
|
}}
|
||||||
@@ -229,7 +283,6 @@ export const SettingsBtn = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -15,17 +15,25 @@ export const handleTrackSubscribed = (
|
|||||||
if (!track.isMuted) {
|
if (!track.isMuted) {
|
||||||
useAudioStore.getState().addSpeakingParticipant(participant);
|
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) {
|
if (track.kind === Track.Kind.Video || track.kind === Track.Kind.Audio) {
|
||||||
// attach it to a new HTMLVideoElement or HTMLAudioElement
|
// attach it to a new HTMLVideoElement or HTMLAudioElement
|
||||||
const element = track.attach();
|
const element = track.attach();
|
||||||
element.play();
|
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) => {
|
export const handleTrackUnsubscribed = (track: RemoteTrack) => {
|
||||||
|
|||||||
@@ -24,8 +24,12 @@ import { getRadioStream } from "_helpers/radioEffect";
|
|||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
type TalkState = {
|
type TalkState = {
|
||||||
micDeviceId: string | null;
|
settings: {
|
||||||
micVolume: number;
|
micDeviceId: string | null;
|
||||||
|
micVolume: number;
|
||||||
|
radioVolume: number;
|
||||||
|
dmeVolume: number;
|
||||||
|
};
|
||||||
isTalking: boolean;
|
isTalking: boolean;
|
||||||
transmitBlocked: boolean;
|
transmitBlocked: boolean;
|
||||||
removeMessage: () => void;
|
removeMessage: () => void;
|
||||||
@@ -34,7 +38,7 @@ type TalkState = {
|
|||||||
connectionQuality: ConnectionQuality;
|
connectionQuality: ConnectionQuality;
|
||||||
remoteParticipants: number;
|
remoteParticipants: number;
|
||||||
toggleTalking: () => void;
|
toggleTalking: () => void;
|
||||||
setMic: (micDeviceId: string | null, volume: number) => void;
|
setSettings: (settings: Partial<TalkState["settings"]>) => void;
|
||||||
connect: (roomName: string, role: string) => void;
|
connect: (roomName: string, role: string) => void;
|
||||||
disconnect: () => void;
|
disconnect: () => void;
|
||||||
speakingParticipants: Participant[];
|
speakingParticipants: Participant[];
|
||||||
@@ -54,9 +58,14 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
localRadioTrack: undefined,
|
localRadioTrack: undefined,
|
||||||
transmitBlocked: false,
|
transmitBlocked: false,
|
||||||
message: null,
|
message: null,
|
||||||
micDeviceId: null,
|
|
||||||
speakingParticipants: [],
|
speakingParticipants: [],
|
||||||
micVolume: 1,
|
micVolume: 1,
|
||||||
|
settings: {
|
||||||
|
micDeviceId: null,
|
||||||
|
micVolume: 1,
|
||||||
|
radioVolume: 0.8,
|
||||||
|
dmeVolume: 0.8,
|
||||||
|
},
|
||||||
state: "disconnected" as const,
|
state: "disconnected" as const,
|
||||||
remoteParticipants: 0,
|
remoteParticipants: 0,
|
||||||
connectionQuality: ConnectionQuality.Unknown,
|
connectionQuality: ConnectionQuality.Unknown,
|
||||||
@@ -84,9 +93,19 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
set({ transmitBlocked: false, message: null, isTalking: true });
|
set({ transmitBlocked: false, message: null, isTalking: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setMic: (micDeviceId, micVolume) => {
|
setSettings: (newSettings) => {
|
||||||
set({ micDeviceId, micVolume });
|
const oldSettings = get().settings;
|
||||||
if (get().state === "connected") {
|
set((s) => ({
|
||||||
|
settings: {
|
||||||
|
...s.settings,
|
||||||
|
...newSettings,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
if (
|
||||||
|
get().state === "connected" &&
|
||||||
|
(oldSettings.micDeviceId !== newSettings.micDeviceId ||
|
||||||
|
oldSettings.micVolume !== newSettings.micVolume)
|
||||||
|
) {
|
||||||
const { room, disconnect, connect } = get();
|
const { room, disconnect, connect } = get();
|
||||||
const role = room?.localParticipant.attributes.role;
|
const role = room?.localParticipant.attributes.role;
|
||||||
console.log(role);
|
console.log(role);
|
||||||
@@ -150,13 +169,13 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
|
|
||||||
const inputStream = await navigator.mediaDevices.getUserMedia({
|
const inputStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
deviceId: get().micDeviceId ?? undefined,
|
deviceId: get().settings.micDeviceId ?? undefined,
|
||||||
noiseSuppression: true,
|
noiseSuppression: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Funk-Effekt anwenden
|
// 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");
|
if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen");
|
||||||
|
|
||||||
const [track] = radioStream.getAudioTracks();
|
const [track] = radioStream.getAudioTracks();
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import {
|
|||||||
LockOpen1Icon,
|
LockOpen1Icon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { UserOptionalDefaults, UserOptionalDefaultsSchema } from "@repo/db/zod";
|
import { CircleAlert, Trash2 } from "lucide-react";
|
||||||
import { Bell, CircleAlert, Plane, Trash2 } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { deleteUser, sendVerificationLink } from "(app)/admin/user/action";
|
import { deleteUser, sendVerificationLink } from "(app)/admin/user/action";
|
||||||
import { setStandardName } from "../../../../helper/discord";
|
import { setStandardName } from "../../../../helper/discord";
|
||||||
|
|
||||||
@@ -458,78 +456,3 @@ export const PasswordForm = (): React.JSX.Element => {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PilotForm = ({ user }: { user: User }): React.JSX.Element | null => {
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
const form = useForm<UserOptionalDefaults>({
|
|
||||||
defaultValues: {
|
|
||||||
...user,
|
|
||||||
emailVerified: user.emailVerified ?? undefined,
|
|
||||||
},
|
|
||||||
resolver: zodResolver(UserOptionalDefaultsSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) return null;
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
className="card-body"
|
|
||||||
onSubmit={form.handleSubmit(async (values) => {
|
|
||||||
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)",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<h2 className="card-title mb-5">
|
|
||||||
<Plane className="w-5 h-5" /> Pilot
|
|
||||||
</h2>
|
|
||||||
<div className="content-center">
|
|
||||||
<label className="floating-label w-full">
|
|
||||||
<span className="text-lg flex items-center gap-2">
|
|
||||||
<Bell /> NTFY room
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
placeholder="Erhalte eine Benachrichtigung auf dein Handy über NTFY"
|
|
||||||
className="input input-bordered w-full"
|
|
||||||
{...form.register("settingsNtfyRoom")}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<p className="label mt-2 w-full">
|
|
||||||
<Link
|
|
||||||
href="https://docs.virtualairrescue.com/docs/Leitstelle/App-Alarmierung#download"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="link link-hover link-primary"
|
|
||||||
>
|
|
||||||
Hier
|
|
||||||
</Link>
|
|
||||||
findest du mehr Informationen!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{form.formState.errors.settingsNtfyRoom && (
|
|
||||||
<p className="text-error">{form.formState.errors.settingsNtfyRoom.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="card-actions justify-center pt-6 mt-auto">
|
|
||||||
<Button
|
|
||||||
className="btn-sm btn-wide btn-outline btn-primary"
|
|
||||||
isLoading={isLoading}
|
|
||||||
disabled={!form.formState.isDirty}
|
|
||||||
role="submit"
|
|
||||||
>
|
|
||||||
<BookmarkIcon /> Speichern
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
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 { GearIcon } from "@radix-ui/react-icons";
|
||||||
import { Error } from "_components/Error";
|
import { Error } from "_components/Error";
|
||||||
|
|
||||||
@@ -48,9 +48,6 @@ export default async function Page() {
|
|||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||||
<PasswordForm />
|
<PasswordForm />
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
|
||||||
<PilotForm user={user} />
|
|
||||||
</div>
|
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||||
<DeleteForm user={user} penaltys={userPenaltys} />
|
<DeleteForm user={user} penaltys={userPenaltys} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user