Nachalarmieren select
Alarmieren aus Einsatz erstellen Maske Map-Tiles SDS sound: Status J SDS Nachricht: public-User Audio: Es kann nur ein Nutzer gleichzeitig Funken Select in Report und Chat: default value -> OnChange
This commit is contained in:
@@ -27,6 +27,7 @@ export const Audio = () => {
|
|||||||
speakingParticipants,
|
speakingParticipants,
|
||||||
isTalking,
|
isTalking,
|
||||||
toggleTalking,
|
toggleTalking,
|
||||||
|
transmitBlocked,
|
||||||
connect,
|
connect,
|
||||||
state,
|
state,
|
||||||
connectionQuality,
|
connectionQuality,
|
||||||
@@ -41,6 +42,7 @@ export const Audio = () => {
|
|||||||
isReceiving: speakingParticipants.length > 0,
|
isReceiving: speakingParticipants.length > 0,
|
||||||
isTransmitting: isTalking,
|
isTransmitting: isTalking,
|
||||||
unpausedTracks: speakingParticipants,
|
unpausedTracks: speakingParticipants,
|
||||||
|
transmitBlocked,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { selectedStation, status: pilotState } = usePilotConnectionStore((state) => state);
|
const { selectedStation, status: pilotState } = usePilotConnectionStore((state) => state);
|
||||||
@@ -154,6 +156,7 @@ export const Audio = () => {
|
|||||||
"btn btn-sm btn-soft border-none hover:bg-inherit",
|
"btn btn-sm btn-soft border-none hover:bg-inherit",
|
||||||
!isTalking && "bg-transparent hover:bg-sky-400/20",
|
!isTalking && "bg-transparent hover:bg-sky-400/20",
|
||||||
isTalking && "bg-green-700 hover:bg-green-600",
|
isTalking && "bg-green-700 hover:bg-green-600",
|
||||||
|
transmitBlocked && "bg-yellow-500 hover:bg-yellow-500",
|
||||||
state === "disconnected" && "bg-red-500 hover:bg-red-500",
|
state === "disconnected" && "bg-red-500 hover:bg-red-500",
|
||||||
state === "error" && "bg-red-500 hover:bg-red-500",
|
state === "error" && "bg-red-500 hover:bg-red-500",
|
||||||
state === "connecting" && "bg-yellow-500 hover:bg-yellow-500 cursor-default",
|
state === "connecting" && "bg-yellow-500 hover:bg-yellow-500 cursor-default",
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ export const useSounds = ({
|
|||||||
isReceiving,
|
isReceiving,
|
||||||
isTransmitting,
|
isTransmitting,
|
||||||
unpausedTracks,
|
unpausedTracks,
|
||||||
|
transmitBlocked,
|
||||||
}: {
|
}: {
|
||||||
isReceiving: boolean;
|
isReceiving: boolean;
|
||||||
isTransmitting: boolean;
|
isTransmitting: boolean;
|
||||||
unpausedTracks: unknown[];
|
unpausedTracks: unknown[];
|
||||||
|
transmitBlocked?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { room } = useAudioStore();
|
const { room } = useAudioStore();
|
||||||
// Sounds as refs
|
// Sounds as refs
|
||||||
@@ -56,6 +58,17 @@ export const useSounds = ({
|
|||||||
}
|
}
|
||||||
}, [isReceiving, isTransmitting, soundConnectionStarted]);
|
}, [isReceiving, isTransmitting, soundConnectionStarted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (transmitBlocked && foreignCallBlocked.current) {
|
||||||
|
foreignCallBlocked.current.volume = 0.2;
|
||||||
|
foreignCallBlocked.current.currentTime = 0;
|
||||||
|
foreignCallBlocked.current.loop = true;
|
||||||
|
foreignCallBlocked.current.play().catch(() => {});
|
||||||
|
} else if (foreignCallBlocked.current) {
|
||||||
|
foreignCallBlocked.current.pause();
|
||||||
|
}
|
||||||
|
}, [transmitBlocked]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isTransmitting && connectionStart.current!.paused) {
|
if (isTransmitting && connectionStart.current!.paused) {
|
||||||
ownCallStarted.current!.volume = 0.2;
|
ownCallStarted.current!.volume = 0.2;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const Chat = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!session.data?.user.id) return;
|
if (!session.data?.user.id) return;
|
||||||
setOwnId(session.data?.user.id);
|
setOwnId(session.data?.user.id);
|
||||||
}, [session.data?.user.id]);
|
}, [session.data?.user.id]);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export const Chat = () => {
|
|||||||
const filteredAircrafts = aircrafts?.filter((a) => a.userId !== session.data?.user.id);
|
const filteredAircrafts = aircrafts?.filter((a) => a.userId !== session.data?.user.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("dropdown dropdown-right", chatOpen && "dropdown-open")}>
|
<div className={cn("dropdown dropdown-right dropdown-center", chatOpen && "dropdown-open")}>
|
||||||
<div className="indicator">
|
<div className="indicator">
|
||||||
{Object.values(chats).some((c) => c.notification) && (
|
{Object.values(chats).some((c) => c.notification) && (
|
||||||
<span className="indicator-item status status-info"></span>
|
<span className="indicator-item status status-info"></span>
|
||||||
@@ -68,9 +68,9 @@ export const Chat = () => {
|
|||||||
{chatOpen && (
|
{chatOpen && (
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="dropdown-content card bg-base-200 w-150 shadow-md z-[1100] ml-2 border-1 border-primary"
|
className="dropdown-content card bg-base-200 w-150 shadow-md z-[1100] max-h-[400px] ml-2 border-1 border-primary"
|
||||||
>
|
>
|
||||||
<div className="card-body">
|
<div className="card-body overflow-y-auto">
|
||||||
<h2 className="inline-flex items-center gap-2 text-lg font-bold mb-2">
|
<h2 className="inline-flex items-center gap-2 text-lg font-bold mb-2">
|
||||||
<ChatBubbleIcon /> Chat
|
<ChatBubbleIcon /> Chat
|
||||||
</h2>
|
</h2>
|
||||||
@@ -118,7 +118,7 @@ export const Chat = () => {
|
|||||||
<span className="text-xl">+</span>
|
<span className="text-xl">+</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="tabs tabs-lift">
|
<div className="tabs tabs-lift max-h-full">
|
||||||
{Object.keys(chats).map((userId) => {
|
{Object.keys(chats).map((userId) => {
|
||||||
const chat = chats[userId];
|
const chat = chats[userId];
|
||||||
if (!chat) return null;
|
if (!chat) return null;
|
||||||
@@ -126,7 +126,6 @@ export const Chat = () => {
|
|||||||
<Fragment key={userId}>
|
<Fragment key={userId}>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="my_tabs_3"
|
|
||||||
className="tab"
|
className="tab"
|
||||||
aria-label={`<${chat.name}>`}
|
aria-label={`<${chat.name}>`}
|
||||||
checked={selectedChat === userId}
|
checked={selectedChat === userId}
|
||||||
@@ -140,7 +139,7 @@ export const Chat = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="tab-content bg-base-100 border-base-300 p-6">
|
<div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto">
|
||||||
{chat.messages.map((chatMessage) => {
|
{chat.messages.map((chatMessage) => {
|
||||||
const isSender = chatMessage.senderId === session.data?.user.id;
|
const isSender = chatMessage.senderId === session.data?.user.id;
|
||||||
return (
|
return (
|
||||||
@@ -173,6 +172,22 @@ export const Chat = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMessage(e.target.value);
|
setMessage(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (message.length < 1) return;
|
||||||
|
if (!selectedChat) return;
|
||||||
|
setSending(true);
|
||||||
|
sendMessage(selectedChat, message)
|
||||||
|
.then(() => {
|
||||||
|
setMessage("");
|
||||||
|
setSending(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setSending(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
value={message}
|
value={message}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ export const Report = () => {
|
|||||||
const filteredAircrafts = aircrafts?.filter((a) => a.userId !== session.data?.user.id);
|
const filteredAircrafts = aircrafts?.filter((a) => a.userId !== session.data?.user.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("dropdown dropdown-right", reportTabOpen && "dropdown-open")}>
|
<div
|
||||||
|
className={cn("dropdown dropdown-right dropdown-center", reportTabOpen && "dropdown-open")}
|
||||||
|
>
|
||||||
<div className="indicator">
|
<div className="indicator">
|
||||||
<button
|
<button
|
||||||
className="btn btn-soft btn-sm btn-error"
|
className="btn btn-soft btn-sm btn-error"
|
||||||
@@ -70,12 +72,11 @@ export const Report = () => {
|
|||||||
Keine Nutzer gefunden
|
Keine Nutzer gefunden
|
||||||
</option>
|
</option>
|
||||||
)}
|
)}
|
||||||
{filteredDispatcher?.length ||
|
{(filteredDispatcher?.length || filteredAircrafts?.length) && (
|
||||||
(filteredAircrafts?.length && (
|
<option disabled value="default">
|
||||||
<option disabled value="default">
|
Nutzer auswählen
|
||||||
Nutzer auswählen
|
</option>
|
||||||
</option>
|
)}
|
||||||
))}
|
|
||||||
|
|
||||||
{filteredDispatcher?.map((dispatcher) => (
|
{filteredDispatcher?.map((dispatcher) => (
|
||||||
<option key={dispatcher.userId} value={dispatcher.userId}>
|
<option key={dispatcher.userId} value={dispatcher.userId}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
|
import "./mapStyles.css";
|
||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import { MapContainer } from "react-leaflet";
|
import { MapContainer } from "react-leaflet";
|
||||||
import { BaseMaps } from "_components/map/BaseMaps";
|
import { BaseMaps } from "_components/map/BaseMaps";
|
||||||
@@ -39,7 +40,7 @@ const Map = () => {
|
|||||||
return (
|
return (
|
||||||
<MapContainer
|
<MapContainer
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="flex-1"
|
className="flex-1 bg-base-200"
|
||||||
center={map.center}
|
center={map.center}
|
||||||
zoom={map.zoom}
|
zoom={map.zoom}
|
||||||
fadeAnimation={false}
|
fadeAnimation={false}
|
||||||
|
|||||||
@@ -397,7 +397,9 @@ const SDSTab = ({
|
|||||||
stationId: aircraft.Station.id,
|
stationId: aircraft.Station.id,
|
||||||
station: aircraft.Station,
|
station: aircraft.Station,
|
||||||
message: note,
|
message: note,
|
||||||
user: getPublicUser(session.data!.user),
|
user: getPublicUser(session.data!.user, {
|
||||||
|
ignorePrivacy: true,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -341,10 +341,10 @@ const Patientdetails = ({ mission }: { mission: Mission }) => {
|
|||||||
|
|
||||||
const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [selectedStation, setSelectedStation] = useState<Station | "RTW" | "POL" | "FW" | null>(
|
const [selectedStation, setSelectedStation] = useState<number | "RTW" | "POL" | "FW" | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const { data: conenctedAircrafts } = useQuery({
|
const { data: connectedAircrafts } = useQuery({
|
||||||
queryKey: ["aircrafts"],
|
queryKey: ["aircrafts"],
|
||||||
queryFn: getConnectedAircraftsAPI,
|
queryFn: getConnectedAircraftsAPI,
|
||||||
});
|
});
|
||||||
@@ -383,17 +383,6 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
queryFn: () => getStationsAPI(),
|
queryFn: () => getStationsAPI(),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (allStations) {
|
|
||||||
const stationsNotItMission = allStations.filter(
|
|
||||||
(s) => !mission.missionStationIds.includes(s.id),
|
|
||||||
);
|
|
||||||
if (stationsNotItMission[0]) {
|
|
||||||
setSelectedStation(stationsNotItMission[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [allStations, mission.missionStationIds]);
|
|
||||||
|
|
||||||
const sendAlertMutation = useMutation({
|
const sendAlertMutation = useMutation({
|
||||||
mutationKey: ["missions"],
|
mutationKey: ["missions"],
|
||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
@@ -421,7 +410,7 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
label: station.bosCallsign,
|
label: station.bosCallsign,
|
||||||
value: station.id,
|
value: station.id,
|
||||||
type: "station" as const,
|
type: "station" as const,
|
||||||
isOnline: !!conenctedAircrafts?.find((a) => a.stationId === station.id),
|
isOnline: !!connectedAircrafts?.find((a) => a.stationId === station.id),
|
||||||
})) || []),
|
})) || []),
|
||||||
...(!mission.hpgFireEngineState || mission.hpgFireEngineState === "NOT_REQUESTED"
|
...(!mission.hpgFireEngineState || mission.hpgFireEngineState === "NOT_REQUESTED"
|
||||||
? [{ label: "Feuerwehr", value: "FW", type: "vehicle" as const }]
|
? [{ label: "Feuerwehr", value: "FW", type: "vehicle" as const }]
|
||||||
@@ -447,18 +436,6 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
return a.label.localeCompare(b.label);
|
return a.label.localeCompare(b.label);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const firstOption = stationsOptions[0];
|
|
||||||
if (!firstOption) {
|
|
||||||
setSelectedStation(null);
|
|
||||||
} else if (firstOption.type === "station") {
|
|
||||||
const station = allStations?.find((s) => s.id === firstOption.value);
|
|
||||||
setSelectedStation(station ?? null);
|
|
||||||
} else {
|
|
||||||
setSelectedStation(firstOption.value as "RTW" | "POL" | "FW");
|
|
||||||
}
|
|
||||||
}, [stationsOptions, allStations]);
|
|
||||||
|
|
||||||
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
|
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
|
||||||
|
|
||||||
const HPGVehicle = ({ state, name }: { state: HpgState; name: string }) => (
|
const HPGVehicle = ({ state, name }: { state: HpgState; name: string }) => (
|
||||||
@@ -506,7 +483,7 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
</div>
|
</div>
|
||||||
<ul className="space-y-2 max-h-[300px] overflow-y-auto overflow-x-auto">
|
<ul className="space-y-2 max-h-[300px] overflow-y-auto overflow-x-auto">
|
||||||
{missionStations?.map((station, index) => {
|
{missionStations?.map((station, index) => {
|
||||||
const connectedAircraft = conenctedAircrafts?.find(
|
const connectedAircraft = connectedAircrafts?.find(
|
||||||
(aircraft) => aircraft.stationId === station.id,
|
(aircraft) => aircraft.stationId === station.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -550,15 +527,15 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
<select
|
<select
|
||||||
className="select select-sm select-primary select-bordered flex-1"
|
className="select select-sm select-primary select-bordered flex-1"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selected = allStations?.find((s) => s.id.toString() === e.target.value);
|
const value = e.target.value;
|
||||||
if (selected) {
|
const parsedValue = !isNaN(Number(value)) ? parseInt(value, 10) : value;
|
||||||
setSelectedStation(selected);
|
setSelectedStation(parsedValue as number | "RTW" | "POL" | "FW" | null);
|
||||||
} else {
|
|
||||||
setSelectedStation(e.target.value as "RTW" | "POL" | "FW");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
value={typeof selectedStation === "string" ? selectedStation : selectedStation?.id}
|
value={selectedStation || "default"}
|
||||||
>
|
>
|
||||||
|
<option disabled value={"default"}>
|
||||||
|
Rettungsmittel auswählen
|
||||||
|
</option>
|
||||||
{stationsOptions.map((option) => (
|
{stationsOptions.map((option) => (
|
||||||
<option
|
<option
|
||||||
key={option.value}
|
key={option.value}
|
||||||
@@ -582,18 +559,18 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
|||||||
vehicleName: selectedStation,
|
vehicleName: selectedStation,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!selectedStation?.id) return;
|
if (!selectedStation) return;
|
||||||
await updateMissionMutation.mutateAsync({
|
await updateMissionMutation.mutateAsync({
|
||||||
id: mission.id,
|
id: mission.id,
|
||||||
missionEdit: {
|
missionEdit: {
|
||||||
missionStationIds: {
|
missionStationIds: {
|
||||||
push: selectedStation?.id,
|
push: selectedStation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await sendAlertMutation.mutate({
|
await sendAlertMutation.mutate({
|
||||||
id: mission.id,
|
id: mission.id,
|
||||||
stationId: selectedStation?.id ?? 0,
|
stationId: selectedStation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,106 +1,3 @@
|
|||||||
.no-pointer {
|
.leaflet-container {
|
||||||
cursor: unset;
|
background: var(--color-base-200) !important;
|
||||||
}
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-tooltip-aircraft {
|
|
||||||
padding: 0 !important;
|
|
||||||
border: 0 !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
color: rgb(254, 254, 254) !important;
|
|
||||||
left: 35px !important;
|
|
||||||
top: 40px !important;
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-popup-content-wrapper {
|
|
||||||
background-color: var(--dark-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in {
|
|
||||||
from {
|
|
||||||
right: -20%;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
right: 0;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-out {
|
|
||||||
from {
|
|
||||||
right: 0;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
right: -20%;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-tooltip-aircraft:before {
|
|
||||||
padding: 0 !important;
|
|
||||||
border: 0 !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
color: rgb(255, 255, 255) !important;
|
|
||||||
left: 0px !important;
|
|
||||||
top: 0px !important;
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-tooltip-bg {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-tooltip-bg:before {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-pointer {
|
|
||||||
cursor: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-tooltip-aircraft {
|
|
||||||
padding: 0 !important;
|
|
||||||
border: 0 !important;
|
|
||||||
background-color: var(--surface) !important;
|
|
||||||
color: var(--on-surface) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-tooltip-aircraft:before {
|
|
||||||
border-bottom-color: var(--dark-surface) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-box {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 50%;
|
|
||||||
right: 50px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.75);
|
|
||||||
border: 5px solid #243671;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px;
|
|
||||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
max-width: 300px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-box-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #243671;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,9 @@ export const handleTrackSubscribed = (
|
|||||||
}
|
}
|
||||||
track.on("unmuted", () => {
|
track.on("unmuted", () => {
|
||||||
useAudioStore.getState().addSpeakingParticipant(participant);
|
useAudioStore.getState().addSpeakingParticipant(participant);
|
||||||
|
|
||||||
console.log("Track unmuted:", track);
|
|
||||||
});
|
});
|
||||||
track.on("muted", () => {
|
track.on("muted", () => {
|
||||||
useAudioStore.getState().removeSpeakingParticipant(participant);
|
useAudioStore.getState().removeSpeakingParticipant(participant);
|
||||||
|
|
||||||
console.log("Track muted:", track);
|
|
||||||
});
|
});
|
||||||
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
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type TalkState = {
|
|||||||
micDeviceId: string | null;
|
micDeviceId: string | null;
|
||||||
micVolume: number;
|
micVolume: number;
|
||||||
isTalking: boolean;
|
isTalking: boolean;
|
||||||
|
transmitBlocked: boolean;
|
||||||
removeMessage: () => void;
|
removeMessage: () => void;
|
||||||
state: "connecting" | "connected" | "disconnected" | "error";
|
state: "connecting" | "connected" | "disconnected" | "error";
|
||||||
message: string | null;
|
message: string | null;
|
||||||
@@ -40,12 +41,12 @@ const getToken = async (roomName: string) => {
|
|||||||
|
|
||||||
export const useAudioStore = create<TalkState>((set, get) => ({
|
export const useAudioStore = create<TalkState>((set, get) => ({
|
||||||
isTalking: false,
|
isTalking: false,
|
||||||
|
transmitBlocked: false,
|
||||||
message: null,
|
message: null,
|
||||||
micDeviceId: null,
|
micDeviceId: null,
|
||||||
speakingParticipants: [],
|
speakingParticipants: [],
|
||||||
micVolume: 1,
|
micVolume: 1,
|
||||||
state: "disconnected",
|
state: "disconnected",
|
||||||
source: "",
|
|
||||||
remoteParticipants: 0,
|
remoteParticipants: 0,
|
||||||
connectionQuality: ConnectionQuality.Unknown,
|
connectionQuality: ConnectionQuality.Unknown,
|
||||||
room: null,
|
room: null,
|
||||||
@@ -61,39 +62,38 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
set({ message: null });
|
set({ message: null });
|
||||||
},
|
},
|
||||||
removeSpeakingParticipant: (participant) => {
|
removeSpeakingParticipant: (participant) => {
|
||||||
|
const newSpeaktingParticipants = get().speakingParticipants.filter(
|
||||||
|
(p) => !(p.identity === participant.identity),
|
||||||
|
);
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
speakingParticipants: state.speakingParticipants.filter(
|
speakingParticipants: newSpeaktingParticipants,
|
||||||
(p) => !(p.identity === participant.identity),
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
|
if (newSpeaktingParticipants.length === 0 && get().transmitBlocked) {
|
||||||
|
get().room?.localParticipant.setMicrophoneEnabled(true);
|
||||||
|
set({ transmitBlocked: false, message: null, isTalking: true });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setMic: (micDeviceId, micVolume) => {
|
setMic: (micDeviceId, micVolume) => {
|
||||||
set({ micDeviceId, micVolume });
|
set({ micDeviceId, micVolume });
|
||||||
},
|
},
|
||||||
toggleTalking: () => {
|
toggleTalking: () => {
|
||||||
const { room, isTalking, micDeviceId, micVolume } = get();
|
const { room, isTalking, micDeviceId, micVolume, speakingParticipants } = get();
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
|
if (speakingParticipants.length > 0 && !isTalking) {
|
||||||
|
// Wenn andere sprechen, nicht reden
|
||||||
|
set({
|
||||||
|
message: "Rufgruppe besetzt",
|
||||||
|
transmitBlocked: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Todo: use micVolume
|
// Todo: use micVolume
|
||||||
room.localParticipant.setMicrophoneEnabled(!isTalking, {
|
room.localParticipant.setMicrophoneEnabled(!isTalking, {
|
||||||
deviceId: micDeviceId ?? undefined,
|
deviceId: micDeviceId ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isTalking) {
|
set((state) => ({ isTalking: !state.isTalking, transmitBlocked: false }));
|
||||||
// If old status was not talking, we need to emit the PTT event
|
|
||||||
if (pilotSocket.connected) {
|
|
||||||
pilotSocket.emit("ptt", {
|
|
||||||
shouldTransmit: true,
|
|
||||||
channel: room.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (dispatchSocket.connected)
|
|
||||||
dispatchSocket.emit("ptt", {
|
|
||||||
shouldTransmit: true,
|
|
||||||
channel: room.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({ isTalking: !state.isTalking }));
|
|
||||||
},
|
},
|
||||||
connect: async (roomName, role) => {
|
connect: async (roomName, role) => {
|
||||||
set({ state: "connecting" });
|
set({ state: "connecting" });
|
||||||
@@ -181,11 +181,21 @@ interface PTTData {
|
|||||||
|
|
||||||
const handlePTT = (data: PTTData) => {
|
const handlePTT = (data: PTTData) => {
|
||||||
const { shouldTransmit, source } = data;
|
const { shouldTransmit, source } = data;
|
||||||
const { room } = useAudioStore.getState();
|
const { room, speakingParticipants } = useAudioStore.getState();
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
|
if (speakingParticipants.length > 0 && shouldTransmit) {
|
||||||
|
// Wenn andere sprechen, nicht reden
|
||||||
|
useAudioStore.setState({
|
||||||
|
message: "Rufgruppe besetzt",
|
||||||
|
transmitBlocked: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
useAudioStore.setState({
|
useAudioStore.setState({
|
||||||
isTalking: shouldTransmit,
|
isTalking: shouldTransmit,
|
||||||
|
transmitBlocked: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldTransmit) {
|
if (shouldTransmit) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { cn } from "_helpers/cn";
|
|||||||
export const MissionForm = () => {
|
export const MissionForm = () => {
|
||||||
const { isEditingMission, editingMissionId, setEditingMission } = usePannelStore();
|
const { isEditingMission, editingMissionId, setEditingMission } = usePannelStore();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { setSearchElements, searchElements } = useMapStore((s) => s);
|
const { setSearchElements, searchElements, setContextMenu } = useMapStore((s) => s);
|
||||||
|
|
||||||
const { data: keywords } = useQuery({
|
const { data: keywords } = useQuery({
|
||||||
queryKey: ["keywords"],
|
queryKey: ["keywords"],
|
||||||
@@ -169,6 +169,8 @@ export const MissionForm = () => {
|
|||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
toast.error(`Fehler beim Starten der HPG-Validierung: ${error.message}`);
|
toast.error(`Fehler beim Starten der HPG-Validierung: ${error.message}`);
|
||||||
});
|
});
|
||||||
|
} else if (alertWhenValid) {
|
||||||
|
await sendAlertMutation.mutateAsync(newMission.id);
|
||||||
}
|
}
|
||||||
return newMission;
|
return newMission;
|
||||||
} else {
|
} else {
|
||||||
@@ -447,12 +449,14 @@ export const MissionForm = () => {
|
|||||||
onClick={form.handleSubmit(async (mission: MissionOptionalDefaults) => {
|
onClick={form.handleSubmit(async (mission: MissionOptionalDefaults) => {
|
||||||
try {
|
try {
|
||||||
const newMission = await saveMission(mission, {
|
const newMission = await saveMission(mission, {
|
||||||
|
createNewMission: true,
|
||||||
alertWhenValid: true,
|
alertWhenValid: true,
|
||||||
});
|
});
|
||||||
if (!validationRequired) {
|
|
||||||
await sendAlertMutation.mutateAsync(newMission.id);
|
|
||||||
}
|
|
||||||
setSearchElements([]); // Reset search elements
|
setSearchElements([]); // Reset search elements
|
||||||
|
setContextMenu(null);
|
||||||
|
toast.success(`Einsatz ${newMission.publicId} erstellt`);
|
||||||
|
form.reset();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
@@ -479,6 +483,7 @@ export const MissionForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setSearchElements([]); // Reset search elements
|
setSearchElements([]); // Reset search elements
|
||||||
|
setContextMenu(null);
|
||||||
toast.success(`Einsatz ${newMission.publicId} erstellt`);
|
toast.success(`Einsatz ${newMission.publicId} erstellt`);
|
||||||
form.reset();
|
form.reset();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import { useEffect, useRef } from "react";
|
|||||||
|
|
||||||
export const useSounds = () => {
|
export const useSounds = () => {
|
||||||
const mrtState = useMrtStore((state) => state);
|
const mrtState = useMrtStore((state) => state);
|
||||||
const { connectedAircraft, selectedStation } = usePilotConnectionStore(
|
const { connectedAircraft, selectedStation } = usePilotConnectionStore((state) => state);
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setPage = useMrtStore((state) => state.setPage);
|
const setPage = useMrtStore((state) => state.setPage);
|
||||||
const MRTstatusSoundRef = useRef<HTMLAudioElement>(null);
|
const MRTstatusSoundRef = useRef<HTMLAudioElement>(null);
|
||||||
@@ -16,9 +14,7 @@ export const useSounds = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
MRTstatusSoundRef.current = new Audio("/sounds/MRT-status.mp3");
|
MRTstatusSoundRef.current = new Audio("/sounds/MRT-status.mp3");
|
||||||
MrtMessageReceivedSoundRef.current = new Audio(
|
MrtMessageReceivedSoundRef.current = new Audio("/sounds/MRT-message-received.mp3");
|
||||||
"/sounds/MRT-message-received.mp3",
|
|
||||||
);
|
|
||||||
MRTstatusSoundRef.current.onended = () => {
|
MRTstatusSoundRef.current.onended = () => {
|
||||||
if (!selectedStation || !connectedAircraft?.fmsStatus) return;
|
if (!selectedStation || !connectedAircraft?.fmsStatus) return;
|
||||||
setPage({
|
setPage({
|
||||||
@@ -29,6 +25,7 @@ export const useSounds = () => {
|
|||||||
};
|
};
|
||||||
MrtMessageReceivedSoundRef.current.onended = () => {
|
MrtMessageReceivedSoundRef.current.onended = () => {
|
||||||
if (!selectedStation || !connectedAircraft?.fmsStatus) return;
|
if (!selectedStation || !connectedAircraft?.fmsStatus) return;
|
||||||
|
if (mrtState.page === "sds") return;
|
||||||
setPage({
|
setPage({
|
||||||
page: "home",
|
page: "home",
|
||||||
station: selectedStation,
|
station: selectedStation,
|
||||||
@@ -36,7 +33,7 @@ export const useSounds = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [connectedAircraft?.fmsStatus, selectedStation, setPage]);
|
}, [connectedAircraft?.fmsStatus, selectedStation, setPage, mrtState.page]);
|
||||||
|
|
||||||
const fmsStatus = connectedAircraft?.fmsStatus || "NaN";
|
const fmsStatus = connectedAircraft?.fmsStatus || "NaN";
|
||||||
|
|
||||||
@@ -48,6 +45,8 @@ export const useSounds = () => {
|
|||||||
} else {
|
} else {
|
||||||
MRTstatusSoundRef.current?.play();
|
MRTstatusSoundRef.current?.play();
|
||||||
}
|
}
|
||||||
|
} else if (mrtState.page === "sds") {
|
||||||
|
MrtMessageReceivedSoundRef.current?.play();
|
||||||
}
|
}
|
||||||
}, [mrtState, fmsStatus, connectedAircraft, selectedStation]);
|
}, [mrtState, fmsStatus, connectedAircraft, selectedStation]);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user