110 lines
4.2 KiB
TypeScript
110 lines
4.2 KiB
TypeScript
import BrowserImageManipulation from 'browser-image-manipulation';
|
|
import L from 'leaflet';
|
|
import type { Chart as NGChart } from 'navigraph/charts';
|
|
import { useEffect, useState, type Dispatch, type FC, type SetStateAction } from 'react';
|
|
import { charts } from '../lib/navigraph';
|
|
|
|
interface SidebarProps {
|
|
airport: Airport;
|
|
terminal: Terminal;
|
|
transitions: Procedure[];
|
|
transition: Procedure | undefined;
|
|
chart: Chart | undefined;
|
|
setTransition: Dispatch<SetStateAction<SidebarProps['transition']>>;
|
|
setChart: Dispatch<SetStateAction<SidebarProps['chart']>>;
|
|
backAction: () => void;
|
|
}
|
|
|
|
export const Sidebar: FC<SidebarProps> = ({
|
|
airport,
|
|
terminal,
|
|
transitions,
|
|
transition,
|
|
chart,
|
|
setTransition,
|
|
setChart,
|
|
backAction,
|
|
}) => {
|
|
const [chartIndex, setChartIndex] = useState<NGChart[]>([]);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []);
|
|
})();
|
|
}, [airport.ICAO]);
|
|
|
|
return (
|
|
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
|
|
<button
|
|
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
|
|
onClick={backAction}
|
|
>
|
|
Go back
|
|
</button>
|
|
|
|
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
|
|
<div className="flex flex-col gap-2">
|
|
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">
|
|
Transitions for <span className="font-bold">{terminal.FullName}</span>
|
|
</div>
|
|
{transitions.map((_procedure) => (
|
|
<button
|
|
key={_procedure.name}
|
|
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${_procedure.name === transition?.name ? 'outline-2' : ''}`}
|
|
onClick={() => {
|
|
if (_procedure.name === transition?.name) return;
|
|
|
|
setTransition(_procedure);
|
|
}}
|
|
>
|
|
{_procedure.name ? _procedure.name : 'ZZZZ'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Charts</div>
|
|
{chartIndex
|
|
.filter((_chart) => _chart.is_georeferenced)
|
|
.map((_chart) => (
|
|
<button
|
|
key={_chart.index_number}
|
|
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
|
|
onClick={async () => {
|
|
if (chart?.index_number === _chart.index_number) return;
|
|
if (!_chart.bounding_boxes) return;
|
|
|
|
const planView = _chart.bounding_boxes.planview;
|
|
|
|
const chartImage = await charts.getChartImage({ chart: _chart, theme: 'light' });
|
|
if (!chartImage) return;
|
|
// Crop
|
|
const dataURL = await new BrowserImageManipulation()
|
|
.loadBlob(chartImage)
|
|
.crop(
|
|
planView.pixels.x2 - planView.pixels.x1,
|
|
planView.pixels.y1 - planView.pixels.y2,
|
|
planView.pixels.x1,
|
|
planView.pixels.y2
|
|
)
|
|
.saveAsImage();
|
|
|
|
const bounds = new L.LatLngBounds(
|
|
[planView.latlng.lat1, planView.latlng.lng1],
|
|
[planView.latlng.lat2, planView.latlng.lng2]
|
|
);
|
|
|
|
setChart({ data: dataURL, index_number: _chart.index_number, bounds });
|
|
}}
|
|
>
|
|
<span className="font-bold">{_chart.index_number}</span>
|
|
<br />
|
|
{_chart.name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|