Load download links from live API

This commit is contained in:
Holden
2026-06-27 00:38:53 -05:00
parent e208f1ad5a
commit b1090d2dc9
7 changed files with 253 additions and 118 deletions

View File

@@ -159,11 +159,13 @@ only screen and (-o-min-device-pixel-ratio: 2/1) {
.masthead-download .subtext span {
margin-right: 4px;
color: rgba(255, 255, 255, .5);
}
.masthead-platforms {
display: inline;
color: #fff !important;
}
/* Home page

View File

@@ -0,0 +1,81 @@
---
import { getLatestDownloads } from "../lib/downloads";
const latestDownloads = await getLatestDownloads();
const downloads = latestDownloads.success ? latestDownloads.data : [];
const defaultDownload = downloads[0];
---
<div class="masthead-download" data-downloads={JSON.stringify(downloads)}>
{
defaultDownload ? (
<a
href={defaultDownload.href}
class="btn btn-primary btn-large"
data-download-button
aria-label={`Download TenWholeYears for ${defaultDownload.label}`}
>
Download for {defaultDownload.label}
</a>
) : (
<a href="/downloads/" class="btn btn-primary btn-large">
View Downloads
</a>
)
}
<div class="subtext">
<span>or</span>
<a href="/downloads/" class="masthead-platforms">Other Platforms</a>
</div>
</div>
<script is:inline>
$(() => {
const $downloadRoot = $("[data-downloads]");
if (!$downloadRoot.length) return;
const $downloadButton = $downloadRoot.find("[data-download-button]");
if (!$downloadButton.length) return;
let downloads = [];
try {
downloads = JSON.parse(
$downloadRoot.attr("data-downloads") || "[]",
);
} catch {
return;
}
const findDownload = (platform) =>
$.grep(downloads, (download) => download.platform === platform)[0];
const getDetectedPlatform = () => {
const userAgentDataPlatform =
navigator.userAgentData?.platform?.toLowerCase() || "";
const userAgent = navigator.userAgent.toLowerCase();
const platform = navigator.platform.toLowerCase();
const browserPlatform = `${userAgentDataPlatform} ${userAgent} ${platform}`;
if (
browserPlatform.includes("android") ||
browserPlatform.includes("oculus") ||
browserPlatform.includes("quest")
) {
return "meta";
}
if (browserPlatform.includes("linux")) return "linux";
if (browserPlatform.includes("win")) return "windows";
return "windows";
};
const download = findDownload(getDetectedPlatform()) || downloads[0];
if (!download) return;
$downloadButton
.attr("href", download.href)
.attr("aria-label", `Download TenWholeYears for ${download.label}`)
.text(`Download for ${download.label}`);
});
</script>

View File

@@ -17,9 +17,9 @@ import Footer from "../components/Footer.astro";
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body>
<script src="/js/jquery-4.0.0.min.js" is:inline></script>
<slot />
<Footer />
<script src="/js/jquery-4.0.0.min.js" is:inline></script>
<script src="/js/bootstrap.min.js" is:inline></script>
</body>
</html>

View File

@@ -9,44 +9,54 @@ export const api = {
builds: {
v1: {
async getLatestVersions(): Promise<ApiResult<LatestBuildsResponse>> {
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/latest`, {
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
if (!response.ok) {
console.error("Failed to load latest versions.", {
status: response.status,
statusText: response.statusText,
try {
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/latest`, {
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
return ApiResult.failure<LatestBuildsResponse>("Failed to load latest versions.");
if (!response.ok) {
console.error("Failed to load latest versions.", {
status: response.status,
statusText: response.statusText,
});
return ApiResult.failure<LatestBuildsResponse>("Failed to load latest versions.");
}
var obj = (await response.json()) as LatestBuildsResponse;
return ApiResult.success(obj);
} catch (error: unknown) {
if (error instanceof Error)
return ApiResult.failure<LatestBuildsResponse>(error.message);
return ApiResult.failure<LatestBuildsResponse>("An unexpected error occured.");
}
var obj = (await response.json()) as LatestBuildsResponse;
return ApiResult.success(obj);
},
async getPatchNotes(): Promise<ApiResult<PatchNote[]>> {
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/patchNotes`, {
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
if (!response.ok) {
console.error("Failed to load account.", {
status: response.status,
statusText: response.statusText,
async getPatchNotes(): Promise<PatchNote[]> {
try {
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/patchNotes`, {
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
});
return ApiResult.failure<PatchNote[]>("Failed to load latest versions.");
if (!response.ok) {
console.error("Failed to load account.", {
status: response.status,
statusText: response.statusText,
});
return [];
}
return (await response.json()) as PatchNote[];
} catch (error) {
return [];
}
var obj = (await response.json()) as PatchNote[];
return ApiResult.success(obj);
}
}
}

66
src/lib/downloads.ts Normal file
View File

@@ -0,0 +1,66 @@
import { TWY_CDN_URL } from "astro:env/server";
import { api } from "./api";
import { ApiResult } from "./types/api";
import type { LatestBuildsResponse } from "./types/api/builds";
export type DownloadPlatform = "windows" | "linux" | "meta";
export type DownloadOption = {
platform: DownloadPlatform;
label: string;
version: string;
href: string;
icon: string;
};
type DownloadPlatformConfig = {
platform: DownloadPlatform;
label: string;
icon: string;
versionKey: keyof LatestBuildsResponse;
};
export const downloadPlatforms: DownloadPlatformConfig[] = [
{
platform: "windows",
label: "Windows",
icon: "/img/platforms/platform-windows.png",
versionKey: "latestWindowsVersion",
},
{
platform: "linux",
label: "Linux",
icon: "/img/platforms/platform-linux.png",
versionKey: "latestLinuxVersion",
},
{
platform: "meta",
label: "Meta Quest",
icon: "/img/platforms/platform-oculus.png",
versionKey: "latestMetaVersion",
},
];
export const buildDownloadOptions = (
latestVersions: LatestBuildsResponse,
): DownloadOption[] =>
downloadPlatforms.map(({ platform, label, icon, versionKey }) => {
const version = latestVersions[versionKey];
return {
platform,
label,
version,
href: `${TWY_CDN_URL}/builds/${encodeURIComponent(version)}/${platform}.zip`,
icon,
};
});
export const getLatestDownloads = async (): Promise<ApiResult<DownloadOption[]>> => {
const latestVersions = await api.builds.v1.getLatestVersions();
if (!latestVersions.success)
return ApiResult.failure<DownloadOption[]>(latestVersions.error);
return ApiResult.success(buildDownloadOptions(latestVersions.data));
};

View File

@@ -1,40 +1,12 @@
---
import { api } from "../lib/api";
import { getLatestDownloads } from "../lib/downloads";
import Layout from "../layouts/Layout.astro";
const downloads = [
{
platform: "Windows",
version: "v1.0.3",
href: "#",
icon: "/img/platforms/platform-windows.png",
checksum: "checksum: REPLACE_ME",
},
{
platform: "Linux",
version: "v1.0.3",
href: "#",
icon: "/img/platforms/platform-linux.png",
checksum: "checksum: REPLACE_ME",
},
{
platform: "Oculus Quest",
version: "v1.0.3",
href: "#",
icon: "/img/platforms/platform-oculus.png",
checksum: "checksum: REPLACE_ME",
},
];
const latestDownloads = await getLatestDownloads();
const downloads = latestDownloads.success ? latestDownloads.data : [];
const patchNotes = [
{
version: "v1.0.3",
date: "6/26/2026",
changes: [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Nunc at augue eu nulla aliquet consectetur et in magna.",
],
}
];
const patchNotes = await api.builds.v1.getPatchNotes();
---
<Layout>
@@ -47,52 +19,65 @@ const patchNotes = [
Choose the build for your platform.
</p>
<div class="row">
{downloads.map((download) => (
<div class="span4">
<div class="well text-center download-card">
<p>
<img
src={download.icon}
alt=""
width="72"
height="72"
/>
</p>
<h3>{download.platform}</h3>
<p>{download.version}</p>
{/* <p class="muted">
<code>{download.checksum}</code>
</p> */}
<p>
<a
href={download.href}
class="btn btn-primary"
aria-label={`Download for ${download.platform}`}
>
Download
</a>
</p>
</div>
{
downloads.length > 0 ? (
<div class="row">
{downloads.map((download) => (
<div class="span4">
<div class="well text-center download-card">
<p>
<img
src={download.icon}
alt=""
width="72"
height="72"
/>
</p>
<h3>{download.label}</h3>
<p>{download.version}</p>
<p>
<a
href={download.href}
class="btn btn-primary"
aria-label={`Download for ${download.label}`}
>
Download
</a>
</p>
</div>
</div>
))}
</div>
))}
</div>
) : (
<div class="alert alert-warning">
Downloads are unavailable right now.
</div>
)
}
<section class="download-patch-notes">
<div class="page-header">
<h2>Patch Notes</h2>
</div>
{patchNotes.map((patchNote) => (
<div class="well patch-note">
<h3>{patchNote.version}</h3>
<p class="muted">{patchNote.date}</p>
<ul>
{patchNote.changes.map((change) => (
<li>{change}</li>
))}
</ul>
</div>
))}
{
patchNotes.length > 0 ? (
patchNotes.map((patchNote) => (
<div class="well patch-note">
<h3>{patchNote.version}</h3>
<p class="muted">{patchNote.date}</p>
<ul>
{patchNote.changes.map((change) => (
<li>{change}</li>
))}
</ul>
</div>
))
) : (
<div class="well patch-note">
<p>No patch notes yet.</p>
</div>
)
}
</section>
</div>
</Layout>

View File

@@ -1,4 +1,5 @@
---
import DownloadButton from "../components/DownloadButton.astro";
import Layout from "../layouts/Layout.astro";
const credits = [
@@ -105,17 +106,7 @@ const credits = [
/>
<h1 class="masthead-title">TenWholeYears</h1>
<p>A love letter to Rec Room on its true 10th anniversary</p>
<div class="masthead-download">
<a href="/downloads/" class="btn btn-primary btn-large">
Download for Windows x64
</a>
<div class="subtext">
<span>or</span>
<a href="/downloads/" class="masthead-platforms"
>Other Platforms</a
>
</div>
</div>
<DownloadButton />
</div>
</div>
<div class="container">