feat: enhance flight tracker with GitHub stars display and improve reset view functionality; update trail history sampling rate

This commit is contained in:
Kewonit
2026-02-14 20:40:21 +05:30
parent 2c60861407
commit 1794a4b678
4 changed files with 92 additions and 12 deletions

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useCallback, useSyncExternalStore } from "react"; import { useState, useCallback, useEffect, useSyncExternalStore } from "react";
import { ErrorBoundary } from "@/components/error-boundary"; import { ErrorBoundary } from "@/components/error-boundary";
import { Map } from "@/components/map/map"; import { Map } from "@/components/map/map";
import { CameraController } from "@/components/map/camera-controller"; import { CameraController } from "@/components/map/camera-controller";
@ -18,11 +18,14 @@ import { CITIES, type City } from "@/lib/cities";
import { findByIata, airportToCity } from "@/lib/airports"; import { findByIata, airportToCity } from "@/lib/airports";
import type { FlightState } from "@/lib/opensky"; import type { FlightState } from "@/lib/opensky";
import type { PickingInfo } from "@deck.gl/core"; import type { PickingInfo } from "@deck.gl/core";
import { Github, Star } from "lucide-react";
const DEFAULT_CITY_ID = "mia"; const DEFAULT_CITY_ID = "mia";
const STYLE_STORAGE_KEY = "aeris:mapStyle"; const STYLE_STORAGE_KEY = "aeris:mapStyle";
const DEFAULT_CITY = CITIES.find((c) => c.id === DEFAULT_CITY_ID) ?? CITIES[0]; const DEFAULT_CITY = CITIES.find((c) => c.id === DEFAULT_CITY_ID) ?? CITIES[0];
const GITHUB_REPO_URL = "https://github.com/kewonit/aeris";
const GITHUB_REPO_API = "https://api.github.com/repos/kewonit/aeris";
const subscribeNoop = () => () => {}; const subscribeNoop = () => () => {};
@ -121,6 +124,29 @@ function FlightTrackerInner() {
const trails = useTrailHistory(flights); const trails = useTrailHistory(flights);
const [hoveredFlight, setHoveredFlight] = useState<FlightState | null>(null); const [hoveredFlight, setHoveredFlight] = useState<FlightState | null>(null);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 }); const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const [repoStars, setRepoStars] = useState<number | null>(null);
useEffect(() => {
let mounted = true;
async function loadRepoStars() {
try {
const res = await fetch(GITHUB_REPO_API, { cache: "no-store" });
if (!res.ok) return;
const data = (await res.json()) as { stargazers_count?: number };
if (mounted && typeof data.stargazers_count === "number") {
setRepoStars(data.stargazers_count);
}
} catch {
/* silent fallback */
}
}
loadRepoStars();
return () => {
mounted = false;
};
}, []);
const handleHover = useCallback((info: PickingInfo<FlightState> | null) => { const handleHover = useCallback((info: PickingInfo<FlightState> | null) => {
if (info?.object) { if (info?.object) {
@ -143,8 +169,12 @@ function FlightTrackerInner() {
}, []); }, []);
const handleResetView = useCallback(() => { const handleResetView = useCallback(() => {
window.dispatchEvent(new CustomEvent("aeris:reset-view")); window.dispatchEvent(
}, []); new CustomEvent("aeris:reset-view", {
detail: { center: activeCity.coordinates },
}),
);
}, [activeCity.coordinates]);
return ( return (
<main className="relative h-screen w-screen overflow-hidden bg-black"> <main className="relative h-screen w-screen overflow-hidden bg-black">
@ -175,6 +205,41 @@ function FlightTrackerInner() {
</div> </div>
<div className="pointer-events-auto absolute right-4 top-4 flex items-center gap-2"> <div className="pointer-events-auto absolute right-4 top-4 flex items-center gap-2">
<a
href={GITHUB_REPO_URL}
target="_blank"
rel="noreferrer"
aria-label="Open GitHub repository"
className="relative inline-flex h-9 w-9 items-center justify-center rounded-xl backdrop-blur-2xl transition-colors"
style={{
borderWidth: 1,
borderColor: "rgb(var(--ui-fg) / 0.06)",
backgroundColor: "rgb(var(--ui-fg) / 0.03)",
color: "rgb(var(--ui-fg) / 0.5)",
}}
title={
repoStars != null
? `GitHub · ${formatStarCount(repoStars)} stars`
: "Open GitHub repository"
}
>
<Github className="h-4 w-4" />
{repoStars != null && (
<span
className="pointer-events-none absolute -bottom-1 -right-1 rounded-full px-1.5 py-0.5 text-[9px] font-semibold tabular-nums"
style={{
backgroundColor: "rgb(var(--ui-bg) / 0.95)",
border: "1px solid rgb(var(--ui-fg) / 0.1)",
color: "rgb(var(--ui-fg) / 0.55)",
}}
>
<span className="flex items-center gap-0.5">
<Star className="h-2 w-2" />
{formatStarCount(repoStars)}
</span>
</span>
)}
</a>
<ControlPanel <ControlPanel
activeCity={activeCity} activeCity={activeCity}
onSelectCity={setActiveCity} onSelectCity={setActiveCity}
@ -226,3 +291,9 @@ function Brand({ isDark }: { isDark: boolean }) {
</span> </span>
); );
} }
function formatStarCount(value: number): string {
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}m`;
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;
return `${value}`;
}

View File

@ -44,9 +44,11 @@ export function CameraController({ city }: { city: City }) {
}); });
}; };
const onResetView = () => { const onResetView = (event: Event) => {
const customEvent = event as CustomEvent<{ center?: [number, number] }>;
const center = customEvent.detail?.center ?? city.coordinates;
map.flyTo({ map.flyTo({
center: city.coordinates, center,
zoom: DEFAULT_ZOOM, zoom: DEFAULT_ZOOM,
pitch: DEFAULT_PITCH, pitch: DEFAULT_PITCH,
bearing: DEFAULT_BEARING, bearing: DEFAULT_BEARING,

View File

@ -14,6 +14,7 @@ const TELEPORT_THRESHOLD = 0.3; // degrees
const TRAIL_BELOW_AIRCRAFT_METERS = 20; const TRAIL_BELOW_AIRCRAFT_METERS = 20;
const STARTUP_TRAIL_POLLS = 3; const STARTUP_TRAIL_POLLS = 3;
const STARTUP_TRAIL_STEP_SEC = 12; const STARTUP_TRAIL_STEP_SEC = 12;
const TRACK_DAMPING = 0.18;
function buildStartupFallbackTrail(f: FlightState): [number, number][] { function buildStartupFallbackTrail(f: FlightState): [number, number][] {
if (f.longitude == null || f.latitude == null) return []; if (f.longitude == null || f.latitude == null) return [];
@ -164,11 +165,16 @@ export function FlightLayers({
const next = new Map<string, Snapshot>(); const next = new Map<string, Snapshot>();
for (const f of flights) { for (const f of flights) {
if (f.longitude != null && f.latitude != null) { if (f.longitude != null && f.latitude != null) {
const prev = newPrev.get(f.icao24);
const rawTrack = f.trueTrack ?? 0;
next.set(f.icao24, { next.set(f.icao24, {
lng: f.longitude, lng: f.longitude,
lat: f.latitude, lat: f.latitude,
alt: f.baroAltitude ?? 0, alt: f.baroAltitude ?? 0,
track: f.trueTrack ?? 0, track:
prev != null
? lerpAngle(prev.track, rawTrack, TRACK_DAMPING)
: rawTrack,
}); });
} }
} }
@ -228,7 +234,7 @@ export function FlightLayers({
const elapsed = performance.now() - dataTimestampRef.current; const elapsed = performance.now() - dataTimestampRef.current;
const rawT = elapsed / ANIM_DURATION_MS; const rawT = elapsed / ANIM_DURATION_MS;
const tPos = Math.min(rawT, 1); const tPos = Math.min(rawT, 1);
const tAngle = smoothStep(tPos); const tAngle = smoothStep(smoothStep(smoothStep(tPos)));
const currentFlights = flightsRef.current; const currentFlights = flightsRef.current;
const currentTrails = trailsRef.current; const currentTrails = trailsRef.current;
@ -402,11 +408,12 @@ export function FlightLayers({
: defaultColor; : defaultColor;
return Array.from({ length: len }, (_, i) => { return Array.from({ length: len }, (_, i) => {
const tVal = len > 1 ? i / (len - 1) : 1; const tVal = len > 1 ? i / (len - 1) : 1;
const fade = Math.pow(tVal, 2.4);
return [ return [
base[0], Math.min(255, base[0] + 22),
base[1], Math.min(255, base[1] + 22),
base[2], Math.min(255, base[2] + 22),
Math.round(70 + tVal * 130), Math.round(20 + fade * 200),
]; ];
}) as [number, number, number, number][]; }) as [number, number, number, number][];
}, },

View File

@ -13,7 +13,7 @@ export type TrailEntry = {
const MAX_POINTS = 40; const MAX_POINTS = 40;
const JUMP_THRESHOLD_DEG = 0.3; const JUMP_THRESHOLD_DEG = 0.3;
export const SAMPLES_PER_SEGMENT = 8; export const SAMPLES_PER_SEGMENT = 16;
const HISTORICAL_BOOTSTRAP_POLLS = 3; const HISTORICAL_BOOTSTRAP_POLLS = 3;
const HISTORICAL_BOOTSTRAP_STEP_SEC = 12; const HISTORICAL_BOOTSTRAP_STEP_SEC = 12;
const BOOTSTRAP_UPDATES = 3; const BOOTSTRAP_UPDATES = 3;