Public
HTTP (deprecated)
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

SpaceX Launch tracker

todo:

  • fetch and persist data on regular basis
  • display data from cache
  • calendar endpoint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@3/jsx */
import { frameHtml } from "https://esm.town/v/moe/frameHtml"
import { getLaunches } from "https://esm.town/v/moe/spacexapi"
import { homeImage, launchImage } from "https://esm.town/v/moe/spaceximage"
import { Hono } from "npm:hono"
const app = new Hono()
export default app.fetch
const baseUrl = "https://moe-spacex.web.val.run"
const homeFrame = {
image: "/image",
buttons: [
{ text: "Upcoming Launches", target: "/frames?launch" },
{ text: "Past Launches", target: "/frames?launch&past" },
],
}
app.get("/", async (c) => {
const url = new URL(c.req.url)
const isPast = url.searchParams.has("past")
const data = await getLaunches()
const launches = isPast ? data.pastLaunches.reverse() : data.launches
// console.log(launches)
const chartUrl = getChartUrl(launches, isPast)
const html = (
<html>
<head>
<title>SpaceX Launches 🚀</title>
<script src="https://cdn.tailwindcss.com" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{frameHtml(homeFrame, baseUrl)}
</head>
<body class="bg-neutral-950 text-neutral-50">
<div class="container mx-auto max-w-4xl p-5">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-10">
<div class="text-3xl font-semibold mb-4 sm:mb-0">SpaceX Launches 🚀</div>
<div class="flex flex-row gap-2">
<Button href="?" selected={!isPast}>Upcoming</Button>
<Button href="?past" selected={isPast}>Past</Button>
</div>
</div>
{launches && (
<div class="max-w-xl mb-10">
<img src={chartUrl} class="max-w-full" />
</div>
)}
{launches && launches.map((l, i) => (
<div key={i} class="mb-6">
<div class="font-semibold">{l.dateText}</div>
<div class="my-1">{l.payloadIcon} {l.payload}{l.customer}</div>
<div class="text-sm font-semibold opacity-60 my-1">{l.type}{l.site}{l.orbit}</div>
<div class="text-sm opacity-60">{l.note}</div>
</div>
))}
</div>
</body>
</html>
)
return c.html(html as any)
})
app.get("/image", async (c) => {
const url = new URL(c.req.url)
const isLaunch = url.searchParams.has("launch")
if (!isLaunch)
return c.body(await homeImage(), 200, { "Content-Type": "image/png" })
const isPast = url.searchParams.has("past")
const index = parseInt(url.searchParams.get("i") || "0")
const data = await getLaunches()
const launches = isPast ? data.pastLaunches.reverse() : data.launches
const l = launches[index]
return c.body(await launchImage(l), 200, { "Content-Type": "image/png" })
})
app.post("/frames", async (c) => {
const url = new URL(c.req.url)
const isLaunch = url.searchParams.has("launch")
if (!isLaunch) return c.html(frameHtml(homeFrame, baseUrl))
const isPast = url.searchParams.has("past")
const index = parseInt(url.searchParams.get("i") || "0")
// const data = await getLaunches()
// const launches = isPast ? data.pastLaunches.reverse() : data.launches
// const l = launches[index]
const pastPostfix = isPast ? "&past" : ""
const frame = {
image: `/image?launch&i=${index}${pastPostfix}`,
buttons: [
(index > 0) && { text: "←", target: `/frames?launch&i=${index - 1}${pastPostfix}` },
{ text: "→", target: `/frames?launch&i=${index + 1}${pastPostfix}` },
ngmi-spacex.web.val.run
August 13, 2024