Added Pilot Filter functionality
This commit is contained in:
@@ -2,10 +2,12 @@
|
|||||||
import { useLeftMenuStore } from "_store/leftMenuStore";
|
import { useLeftMenuStore } from "_store/leftMenuStore";
|
||||||
import { cn } from "@repo/shared-components";
|
import { cn } from "@repo/shared-components";
|
||||||
import { SettingsIcon } from "lucide-react";
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
export const SettingsBoard = () => {
|
export const SettingsBoard = () => {
|
||||||
const { setSituationTabOpen, situationTabOpen } = useLeftMenuStore();
|
const { setSituationTabOpen, situationTabOpen } = useLeftMenuStore();
|
||||||
|
const { followOwnAircraft, showOtherAircrafts, showOtherMissions, setMapOptions } =
|
||||||
|
usePilotConnectionStore();
|
||||||
const cross = (
|
const cross = (
|
||||||
<svg
|
<svg
|
||||||
aria-label="disabled"
|
aria-label="disabled"
|
||||||
@@ -59,7 +61,11 @@ export const SettingsBoard = () => {
|
|||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="toggle text-base-content">
|
<label className="toggle text-base-content">
|
||||||
<input type="checkbox" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={followOwnAircraft}
|
||||||
|
onChange={(e) => setMapOptions({ followOwnAircraft: e.target.checked })}
|
||||||
|
/>
|
||||||
{cross}
|
{cross}
|
||||||
{check}
|
{check}
|
||||||
</label>
|
</label>
|
||||||
@@ -67,7 +73,11 @@ export const SettingsBoard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="toggle text-base-content">
|
<label className="toggle text-base-content">
|
||||||
<input type="checkbox" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showOtherAircrafts}
|
||||||
|
onChange={(e) => setMapOptions({ showOtherAircrafts: e.target.checked })}
|
||||||
|
/>
|
||||||
{cross}
|
{cross}
|
||||||
{check}
|
{check}
|
||||||
</label>
|
</label>
|
||||||
@@ -75,7 +85,11 @@ export const SettingsBoard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="toggle text-base-content">
|
<label className="toggle text-base-content">
|
||||||
<input type="checkbox" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showOtherMissions}
|
||||||
|
onChange={(e) => setMapOptions({ showOtherMissions: e.target.checked })}
|
||||||
|
/>
|
||||||
{cross}
|
{cross}
|
||||||
{check}
|
{check}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getMissionsAPI } from "_querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
const AircraftPopupContent = ({
|
const AircraftPopupContent = ({
|
||||||
aircraft,
|
aircraft,
|
||||||
@@ -396,10 +397,42 @@ export const AircraftLayer = () => {
|
|||||||
queryFn: () => getConnectedAircraftsAPI(),
|
queryFn: () => getConnectedAircraftsAPI(),
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 10_000,
|
||||||
});
|
});
|
||||||
|
const { setMap } = useMapStore((state) => state);
|
||||||
|
const map = useMap();
|
||||||
|
const {
|
||||||
|
connectedAircraft,
|
||||||
|
status: pilotConnectionStatus,
|
||||||
|
showOtherAircrafts,
|
||||||
|
followOwnAircraft,
|
||||||
|
} = usePilotConnectionStore((state) => state);
|
||||||
|
|
||||||
|
const filteredAircrafts = useMemo(() => {
|
||||||
|
if (!aircrafts) return [];
|
||||||
|
return aircrafts.filter((aircraft) => {
|
||||||
|
if (pilotConnectionStatus === "connected" && !showOtherAircrafts) {
|
||||||
|
return connectedAircraft?.stationId === aircraft.stationId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [aircrafts, pilotConnectionStatus, connectedAircraft, showOtherAircrafts]);
|
||||||
|
|
||||||
|
const ownAircraft = useMemo(() => {
|
||||||
|
return aircrafts?.find((aircraft) => aircraft.id === connectedAircraft?.id);
|
||||||
|
}, [aircrafts, connectedAircraft]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pilotConnectionStatus === "connected" && followOwnAircraft && ownAircraft) {
|
||||||
|
if (!ownAircraft.posLat || !ownAircraft.posLng) return;
|
||||||
|
setMap({
|
||||||
|
center: [ownAircraft.posLat, ownAircraft.posLng],
|
||||||
|
zoom: map.getZoom(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pilotConnectionStatus, followOwnAircraft, ownAircraft, setMap, map]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{aircrafts?.map((aircraft) => {
|
{filteredAircrafts?.map((aircraft) => {
|
||||||
return <AircraftMarker key={aircraft.id} aircraft={aircraft} />;
|
return <AircraftMarker key={aircraft.id} aircraft={aircraft} />;
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { getMissionsAPI } from "_querys/missions";
|
|||||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = {
|
export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = {
|
||||||
draft: "#0092b8",
|
draft: "#0092b8",
|
||||||
@@ -396,6 +397,11 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
|
|||||||
export const MissionLayer = () => {
|
export const MissionLayer = () => {
|
||||||
const dispatchState = useDispatchConnectionStore((s) => s);
|
const dispatchState = useDispatchConnectionStore((s) => s);
|
||||||
const dispatcherConnected = dispatchState.status === "connected";
|
const dispatcherConnected = dispatchState.status === "connected";
|
||||||
|
const {
|
||||||
|
status: pilotConnectionStatus,
|
||||||
|
showOtherMissions,
|
||||||
|
selectedStation,
|
||||||
|
} = usePilotConnectionStore((state) => state);
|
||||||
|
|
||||||
const { data: missions = [] } = useQuery({
|
const { data: missions = [] } = useQuery({
|
||||||
queryKey: ["missions"],
|
queryKey: ["missions"],
|
||||||
@@ -410,9 +416,18 @@ export const MissionLayer = () => {
|
|||||||
return missions.filter((m: Mission) => {
|
return missions.filter((m: Mission) => {
|
||||||
if (m.state === "draft" && !dispatcherConnected) return false;
|
if (m.state === "draft" && !dispatcherConnected) return false;
|
||||||
if (dispatchState.hideDraftMissions && m.state === "draft") return false;
|
if (dispatchState.hideDraftMissions && m.state === "draft") return false;
|
||||||
|
if (pilotConnectionStatus === "connected" && !showOtherMissions)
|
||||||
|
return m.missionStationIds.includes(selectedStation!.id);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [missions, dispatcherConnected, dispatchState.hideDraftMissions]);
|
}, [
|
||||||
|
missions,
|
||||||
|
dispatcherConnected,
|
||||||
|
dispatchState.hideDraftMissions,
|
||||||
|
pilotConnectionStatus,
|
||||||
|
showOtherMissions,
|
||||||
|
selectedStation,
|
||||||
|
]);
|
||||||
|
|
||||||
// IDEA: Add Marker to Map Layer / LayerGroup
|
// IDEA: Add Marker to Map Layer / LayerGroup
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getMissionsAPI } from "_querys/missions";
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useMap } from "react-leaflet";
|
import { useMap } from "react-leaflet";
|
||||||
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
const PopupContent = ({
|
const PopupContent = ({
|
||||||
aircrafts,
|
aircrafts,
|
||||||
@@ -136,6 +137,7 @@ const PopupContent = ({
|
|||||||
export const MarkerCluster = () => {
|
export const MarkerCluster = () => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const dispatchState = useDispatchConnectionStore((s) => s);
|
const dispatchState = useDispatchConnectionStore((s) => s);
|
||||||
|
const pilotState = usePilotConnectionStore((s) => s);
|
||||||
const dispatcherConnected = dispatchState.status === "connected";
|
const dispatcherConnected = dispatchState.status === "connected";
|
||||||
const { data: aircrafts } = useQuery({
|
const { data: aircrafts } = useQuery({
|
||||||
queryKey: ["aircrafts"],
|
queryKey: ["aircrafts"],
|
||||||
@@ -155,9 +157,36 @@ export const MarkerCluster = () => {
|
|||||||
return missions.filter((m: Mission) => {
|
return missions.filter((m: Mission) => {
|
||||||
if (m.state === "draft" && !dispatcherConnected) return false;
|
if (m.state === "draft" && !dispatcherConnected) return false;
|
||||||
if (dispatchState.hideDraftMissions && m.state === "draft") return false;
|
if (dispatchState.hideDraftMissions && m.state === "draft") return false;
|
||||||
|
if (
|
||||||
|
pilotState.status === "connected" &&
|
||||||
|
!pilotState.showOtherMissions &&
|
||||||
|
pilotState.selectedStation
|
||||||
|
)
|
||||||
|
return m.missionStationIds.includes(pilotState.selectedStation.id);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [missions, dispatcherConnected, dispatchState.hideDraftMissions]);
|
}, [
|
||||||
|
missions,
|
||||||
|
dispatcherConnected,
|
||||||
|
dispatchState.hideDraftMissions,
|
||||||
|
pilotState.selectedStation,
|
||||||
|
pilotState.showOtherMissions,
|
||||||
|
pilotState.status,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredAircrafts = useMemo(() => {
|
||||||
|
return aircrafts?.filter((a: ConnectedAircraft) => {
|
||||||
|
if (pilotState.status === "connected" && !pilotState.showOtherAircrafts) {
|
||||||
|
return a.stationId === pilotState.connectedAircraft?.stationId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
aircrafts,
|
||||||
|
pilotState.status,
|
||||||
|
pilotState.showOtherAircrafts,
|
||||||
|
pilotState.connectedAircraft?.stationId,
|
||||||
|
]);
|
||||||
|
|
||||||
// Track zoom level in state
|
// Track zoom level in state
|
||||||
const [zoom, setZoom] = useState(() => map.getZoom());
|
const [zoom, setZoom] = useState(() => map.getZoom());
|
||||||
@@ -178,7 +207,7 @@ export const MarkerCluster = () => {
|
|||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
aircrafts?.forEach((aircraft) => {
|
filteredAircrafts?.forEach((aircraft) => {
|
||||||
const lat = aircraft.posLat!;
|
const lat = aircraft.posLat!;
|
||||||
const lng = aircraft.posLng!;
|
const lng = aircraft.posLng!;
|
||||||
|
|
||||||
@@ -255,7 +284,7 @@ export const MarkerCluster = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return clusterWithAvgPos;
|
return clusterWithAvgPos;
|
||||||
}, [aircrafts, filteredMissions, zoom]);
|
}, [filteredAircrafts, filteredMissions, zoom]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AudioTrack, RemoteAudioTrack, RemoteTrack } from "livekit-client";
|
||||||
|
|
||||||
// Helper function for distortion curve generation
|
// Helper function for distortion curve generation
|
||||||
function createDistortionCurve(amount: number): Float32Array {
|
function createDistortionCurve(amount: number): Float32Array {
|
||||||
const k = typeof amount === "number" ? amount : 50;
|
const k = typeof amount === "number" ? amount : 50;
|
||||||
@@ -12,10 +14,12 @@ function createDistortionCurve(amount: number): Float32Array {
|
|||||||
return curve;
|
return curve;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRadioStream = (stream: MediaStream, volume: number): MediaStream | null => {
|
export const getRadioStream = (track: RemoteAudioTrack, volume: number): MediaStream | null => {
|
||||||
try {
|
try {
|
||||||
const audioContext = new window.AudioContext();
|
const audioContext = new window.AudioContext();
|
||||||
const sourceNode = audioContext.createMediaStreamSource(stream);
|
const sourceNode = audioContext.createMediaStreamSource(
|
||||||
|
new MediaStream([track.mediaStreamTrack]),
|
||||||
|
);
|
||||||
const destinationNode = audioContext.createMediaStreamDestination();
|
const destinationNode = audioContext.createMediaStreamDestination();
|
||||||
const gainNode = audioContext.createGain();
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ export interface MapStore {
|
|||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
map: {
|
map: {
|
||||||
center: L.LatLngExpression;
|
center: L.LatLngExpression;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ interface ConnectionStore {
|
|||||||
debug?: boolean,
|
debug?: boolean,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
disconnect: () => void;
|
disconnect: () => void;
|
||||||
|
followOwnAircraft: boolean;
|
||||||
|
showOtherAircrafts: boolean;
|
||||||
|
showOtherMissions: boolean;
|
||||||
|
setMapOptions: (options: {
|
||||||
|
followOwnAircraft?: boolean;
|
||||||
|
showOtherAircrafts?: boolean;
|
||||||
|
showOtherMissions?: boolean;
|
||||||
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
||||||
@@ -37,7 +45,15 @@ export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
|||||||
connectedAircraft: null,
|
connectedAircraft: null,
|
||||||
activeMission: null,
|
activeMission: null,
|
||||||
debug: false,
|
debug: false,
|
||||||
|
followOwnAircraft: false,
|
||||||
|
showOtherAircrafts: false,
|
||||||
|
showOtherMissions: false,
|
||||||
|
setMapOptions(options) {
|
||||||
|
set((state) => ({
|
||||||
|
...state,
|
||||||
|
...options,
|
||||||
|
}));
|
||||||
|
},
|
||||||
connect: async (uid, stationId, logoffTime, station, user, debug) =>
|
connect: async (uid, stationId, logoffTime, station, user, debug) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
set({
|
set({
|
||||||
|
|||||||
Reference in New Issue
Block a user