Skip to content

Commit c3e29dd

Browse files
Merge pull request #38 from sebastiankrll/feature/controller-panel
Feature/controller panel
2 parents cf5a43e + 8340d8e commit c3e29dd

16 files changed

Lines changed: 355 additions & 114 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import SectorPanel from "@/components/Panels/Sector/SectorPanel";
2+
3+
export default async function Page(props: { params: Promise<{ callsign: string }> }) {
4+
const params = await props.params;
5+
const callsign = params.callsign.toUpperCase();
6+
7+
return <SectorPanel callsign={callsign} />;
8+
}

apps/web/components/Map/components/Overlay/Overlays.css

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
gap: 0.2rem;
55
position: relative;
66
border: none;
7-
user-select: none;
87
max-width: 300px;
98
}
109

@@ -49,11 +48,16 @@
4948
cursor: pointer;
5049
}
5150

51+
.overlay-live-item.controller.active {
52+
background-color: var(--color-green);
53+
}
54+
5255
.overlay-live-item.controller:hover {
5356
background-color: var(--color-main-text);
5457
}
5558

56-
.overlay-live-item.controller:hover * {
59+
.overlay-live-item.controller:hover *,
60+
.overlay-live-item.controller.active * {
5761
color: white;
5862
}
5963

@@ -83,6 +87,10 @@
8387
text-align: end;
8488
}
8589

90+
.overlay-controller-save {
91+
margin-left: 4px;
92+
}
93+
8694
.overlay-main-wrapper {
8795
display: flex;
8896
background: var(--color-red);

apps/web/components/Map/components/Overlay/Overlays.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ export function getControllerColor(facility: number): string {
8484
}
8585
}
8686

87+
function copyControllerAtisToClipboard(controller: ControllerShort | undefined) {
88+
const atis = controller?.atis?.join("\n") || "";
89+
navigator.clipboard.writeText(atis);
90+
}
91+
8792
export function AirportOverlay({
8893
cached,
8994
short,
@@ -93,29 +98,46 @@ export function AirportOverlay({
9398
short: Required<AirportShort> | null;
9499
merged: ControllerMerged | null;
95100
}) {
96-
const [activeController, setActiveController] = useState(null as string | null);
101+
const [hoveredController, setHoveredController] = useState(null as string | null);
102+
const [clickedController, setClickedController] = useState(null as string | null);
103+
const [copied, setCopied] = useState(false);
97104

98105
const controllers = merged?.controllers as Required<ControllerShort>[] | undefined;
99106
const sortedControllers = controllers?.sort((a, b) => b.facility - a.facility);
100107

108+
const handleCopyClick = () => {
109+
copyControllerAtisToClipboard(sortedControllers?.find((c) => c.callsign === hoveredController));
110+
setCopied(true);
111+
setTimeout(() => setCopied(false), 2000);
112+
};
113+
101114
return (
102115
<div className="overlay-wrapper">
103-
{activeController && (
116+
{(clickedController || hoveredController) && (
104117
<div className="overlay-atis">
105118
<div className="overlay-atis-item">
106-
{sortedControllers?.find((c) => c.callsign === activeController)?.atis?.join("\n") || "Currently unavailable"}
119+
{sortedControllers?.find((c) => c.callsign === clickedController || c.callsign === hoveredController)?.atis?.join("\n") ||
120+
"Currently unavailable"}
107121
</div>
108122
</div>
109123
)}
110124
{sortedControllers && sortedControllers.length > 0 && (
111-
<div className="overlay-live controller" onPointerLeave={() => setActiveController(null)}>
125+
<div className="overlay-live controller" onPointerLeave={() => setHoveredController(null)}>
112126
{sortedControllers?.map((c) => {
113127
return (
114-
<div key={c.callsign} className="overlay-live-item controller" onPointerEnter={() => setActiveController(c.callsign)}>
128+
<div
129+
key={c.callsign}
130+
className={`overlay-live-item controller${clickedController === c.callsign ? " active" : ""}`}
131+
onPointerEnter={() => setHoveredController(c.callsign)}
132+
onClick={() => setClickedController(c.callsign)}
133+
>
115134
<div className="overlay-controller-color" style={{ backgroundColor: getControllerColor(c.facility) }}></div>
116135
<div className="overlay-controller-callsign">{c.callsign}</div>
117136
<div className="overlay-controller-frequency">{(c.frequency / 1000).toFixed(3)}</div>
118137
<div className="overlay-controller-connections">{c.connections}</div>
138+
<button type="button" className="overlay-controller-save" onClick={handleCopyClick}>
139+
{copied && hoveredController === c.callsign ? "✅" : "📋"}
140+
</button>
119141
</div>
120142
);
121143
})}
@@ -160,28 +182,45 @@ export function AirportOverlay({
160182
}
161183

162184
export function SectorOverlay({ cached, merged }: { cached: SimAwareTraconFeature | FIRFeature | null; merged: ControllerMerged | null }) {
163-
const [activeController, setActiveController] = useState(null as string | null);
185+
const [hoveredController, setHoveredController] = useState(null as string | null);
186+
const [clickedController, setClickedController] = useState(null as string | null);
187+
const [copied, setCopied] = useState(false);
164188

165189
const controllers = merged?.controllers as Required<ControllerShort>[] | undefined;
166190

191+
const handleCopyClick = () => {
192+
copyControllerAtisToClipboard(controllers?.find((c) => c.callsign === hoveredController));
193+
setCopied(true);
194+
setTimeout(() => setCopied(false), 2000);
195+
};
196+
167197
return (
168198
<div className="overlay-wrapper">
169-
{activeController && (
199+
{(clickedController || hoveredController) && (
170200
<div className="overlay-atis">
171201
<div className="overlay-atis-item">
172-
{controllers?.find((c) => c.callsign === activeController)?.atis?.join("\n") || "Currently unavailable"}
202+
{controllers?.find((c) => c.callsign === clickedController || c.callsign === hoveredController)?.atis?.join("\n") ||
203+
"Currently unavailable"}
173204
</div>
174205
</div>
175206
)}
176207
{controllers && controllers.length > 0 && (
177-
<div className="overlay-live controller" onPointerLeave={() => setActiveController(null)}>
208+
<div className="overlay-live controller" onPointerLeave={() => setHoveredController(null)}>
178209
{controllers?.map((c) => {
179210
return (
180-
<div key={c.callsign} className="overlay-live-item controller" onPointerEnter={() => setActiveController(c.callsign)}>
211+
<div
212+
key={c.callsign}
213+
className={`overlay-live-item controller${clickedController === c.callsign ? " active" : ""}`}
214+
onPointerEnter={() => setHoveredController(c.callsign)}
215+
onClick={() => setClickedController(c.callsign)}
216+
>
181217
<div className="overlay-controller-color" style={{ backgroundColor: getControllerColor(c.facility) }}></div>
182218
<div className="overlay-controller-callsign">{c.callsign}</div>
183219
<div className="overlay-controller-frequency">{(c.frequency / 1000).toFixed(3)}</div>
184220
<div className="overlay-controller-connections">{c.connections}</div>
221+
<button type="button" className="overlay-controller-save" onClick={handleCopyClick}>
222+
{copied && hoveredController === c.callsign ? "✅" : "📋"}
223+
</button>
185224
</div>
186225
);
187226
})}

apps/web/components/Map/utils/controllerFeatures.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getCachedAirport, getCachedFir, getCachedTracon } from "@/storage/cache
1212
import type { AirportLabelProperties, ControllerLabelProperties } from "@/types/ol";
1313
import { getAirportSize } from "./airportFeatures";
1414
import { airportLabelSource, controllerLabelSource, firSource, traconSource } from "./dataLayers";
15+
import { getMapView } from "./init";
1516

1617
export function getControllerLabelStyle(feature: FeatureLike, resolution: number): Style {
1718
const label = feature.get("label") as string;
@@ -47,7 +48,7 @@ const readGeoJSONFeature = (geojson: SimAwareTraconFeature | FIRFeature, type: "
4748
}) as Feature<MultiPolygon>;
4849

4950
feature.setProperties({ type });
50-
feature.setId(`controller_${id}`);
51+
feature.setId(`sector_${id}`);
5152
return feature;
5253
};
5354

@@ -78,7 +79,7 @@ export async function initControllerFeatures(data: WsAll): Promise<void> {
7879
const feature = new Feature(polygon);
7980

8081
feature.setProperties({ type: "tracon" });
81-
feature.setId(`controller_${id}`);
82+
feature.setId(`sector_${id}`);
8283

8384
traconSource.addFeature(feature);
8485
cachedTracons.set(id, feature);
@@ -148,7 +149,7 @@ export async function updateControllerFeatures(delta: ControllerDelta): Promise<
148149
const feature = new Feature(polygon);
149150

150151
feature.setProperties({ type: "tracon" });
151-
feature.setId(`controller_${id}`);
152+
feature.setId(`sector_${id}`);
152153

153154
traconSource.addFeature(feature);
154155
cachedTracons.set(id, feature);
@@ -179,7 +180,7 @@ export async function updateControllerFeatures(delta: ControllerDelta): Promise<
179180
const shortId = id.replace(/^(tracon_|airport_|fir_)/, "");
180181
controllerList.delete(id);
181182

182-
const labelFeature = controllerLabelSource.getFeatureById(`controller_${shortId}`);
183+
const labelFeature = controllerLabelSource.getFeatureById(`sector_${shortId}`);
183184
if (labelFeature) {
184185
controllerLabelSource.removeFeature(labelFeature);
185186
}
@@ -199,7 +200,7 @@ export async function updateControllerFeatures(delta: ControllerDelta): Promise<
199200
}
200201

201202
if (id.startsWith("airport_")) {
202-
const airportLabel = airportLabelSource.getFeatureById(`controller_${shortId}`);
203+
const airportLabel = airportLabelSource.getFeatureById(`sector_${shortId}`);
203204
if (airportLabel) {
204205
airportLabelSource.removeFeature(airportLabel);
205206
}
@@ -220,7 +221,7 @@ function createControllerLabel(lon: number, lat: number, label: string, type: "t
220221
};
221222

222223
labelFeature.setProperties(props);
223-
labelFeature.setId(`controller_${label}`);
224+
labelFeature.setId(`sector_${label}`);
224225
controllerLabelSource.addFeature(labelFeature);
225226
}
226227

@@ -250,13 +251,13 @@ async function createAirportLabel(controllerMerged: ControllerMerged): Promise<v
250251
};
251252

252253
labelFeature.setProperties(props);
253-
labelFeature.setId(`controller_${id}`);
254+
labelFeature.setId(`sector_${id}`);
254255
airportLabelSource.addFeature(labelFeature);
255256
}
256257

257258
function updateAirportLabel(controllerMerged: ControllerMerged): void {
258259
const id = controllerMerged.id.replace(/^(tracon_|airport_|fir_)/, "");
259-
const labelFeature = airportLabelSource.getFeatureById(`controller_${id}`);
260+
const labelFeature = airportLabelSource.getFeatureById(`sector_${id}`);
260261
if (!labelFeature) return;
261262

262263
const stations = getAirportLabelStations(controllerMerged);
@@ -282,3 +283,21 @@ function getAirportLabelStations(controllerMerged: ControllerMerged): number[] {
282283

283284
return stations;
284285
}
286+
287+
export function moveToSectorFeature(id: string): Feature<Point> | null {
288+
console.log(controllerLabelSource.getFeatures());
289+
const labelFeature = controllerLabelSource.getFeatureById(`sector_${id}`) as Feature<Point> | null;
290+
291+
const view = getMapView();
292+
const geom = labelFeature?.getGeometry();
293+
const coords = geom?.getCoordinates();
294+
if (!coords) return null;
295+
296+
view?.animate({
297+
center: coords,
298+
duration: 200,
299+
zoom: 7,
300+
});
301+
302+
return labelFeature;
303+
}

apps/web/components/Map/utils/dataLayers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function initDataLayers(): (WebGLVectorLayer | VectorLayer)[] {
9797
source: controllerLabelSource,
9898
style: getControllerLabelStyle,
9999
properties: {
100-
type: "controller_label",
100+
type: "sector_label",
101101
},
102102
zIndex: 9,
103103
});

0 commit comments

Comments
 (0)