Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.3",
Expand Down
71 changes: 71 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 101 additions & 0 deletions src/components/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";

import { Alert, AlertTitle, AlertDescription } from "./ui/alert";

import { ScrollArea } from "@/components/ui/scroll-area";

import { CircleAlert } from "lucide-react";

interface AboutDialogProps {
children: React.ReactNode;
}

export function AboutDialog({ children }: AboutDialogProps) {
return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>EO Predictor</DialogTitle>
<DialogDescription>
Potential upcoming observations of Earth.
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[60vh] overflow-y-auto pr-4">
<div className="space-y-4">
<h4 className="text-sm font-medium">About</h4>
<p className="text-sm text-muted-foreground">
EO Predictor visualizes upcoming potential observations of a
curated set of satellites that are relevant for humanitarians and
disaster responders. Using a satellite's location and the "swath
width" (width of the area the satellite's sensor captures), the
coverage of potential observations can be predicted.
</p>
<Alert className="bg-blue-500/10 dark:bg-blue-600/30 border-blue-300 dark:border-blue-600/70">
<CircleAlert className="h-4 w-4 !text-blue-800" />
<AlertTitle className="!text-blue-800">
Actual observations may vary.
</AlertTitle>
<AlertDescription className="font-light !text-blue-800">
Actual earth observations depend on many factors including the
angle of observation of the sensor, potential tasking
prioritization, and potential geopolitical factors. Weather and
daylight conditions can also impact the usability of
observations.
</AlertDescription>
</Alert>
<div className="space-y-2">
<h4 className="text-sm font-medium">Features</h4>
<ul className="text-sm text-muted-foreground space-y-1">
<li>• Two-day prediction window, calculated every 24h.</li>
<li>
• Interactive 3D globe visualization and optional user
geolocation.
</li>
<li>
• Filter by temporal range, satellite constellation, operator,
and sensor type.
</li>
<li>
• Upcoming passes within the map view and that meet the filter
criteria are listed in chronological order.
</li>
</ul>
</div>
<div className="pt-4 border-t">
<p className="text-xs text-muted-foreground">
Data powered by Skyfield orbital calculations and Two-Line
Element (TLE) data from{" "}
<a
href="https://celestrak.org/"
rel="noopener noreferrer"
target="_blank"
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Celestrak
</a>
. Built by{" "}
<a
href="https://www.developmentseed.com/"
rel="noopener noreferrer"
target="_blank"
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Development Seed
</a>
.
</p>
</div>
</div>
</ScrollArea>
</DialogContent>
</Dialog>
);
}
14 changes: 13 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@ import {
NavigationMenuItem,
NavigationMenuLink,
} from "@/components/ui/navigation-menu";
import { AboutDialog } from "@/components/About";
import { Satellite } from "lucide-react";

export function Header() {
return (
<header className="w-full border-b bg-background px-4 py-3">
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold">EO Predictor</h1>
<div className="flex items-center space-x-2 gap-2">
<Satellite />
<h1 className="text-xl font-bold">EO Predictor</h1>
</div>
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<AboutDialog>
<NavigationMenuLink className="cursor-pointer">
About
</NavigationMenuLink>
</AboutDialog>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
href="https://github.com/developmentseed/eo-predictor"
Expand Down
66 changes: 66 additions & 0 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: {
variant: "default",
},
}
)

function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}

function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
className
)}
{...props}
/>
)
}

function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
className
)}
{...props}
/>
)
}

export { Alert, AlertTitle, AlertDescription }
Loading