Load download links from live API
This commit is contained in:
@@ -159,11 +159,13 @@ only screen and (-o-min-device-pixel-ratio: 2/1) {
|
|||||||
|
|
||||||
.masthead-download .subtext span {
|
.masthead-download .subtext span {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
color: rgba(255, 255, 255, .5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.masthead-platforms {
|
.masthead-platforms {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Home page
|
/* Home page
|
||||||
|
|||||||
81
src/components/DownloadButton.astro
Normal file
81
src/components/DownloadButton.astro
Normal 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>
|
||||||
@@ -17,9 +17,9 @@ import Footer from "../components/Footer.astro";
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script src="/js/jquery-4.0.0.min.js" is:inline></script>
|
||||||
<slot />
|
<slot />
|
||||||
<Footer />
|
<Footer />
|
||||||
<script src="/js/jquery-4.0.0.min.js" is:inline></script>
|
|
||||||
<script src="/js/bootstrap.min.js" is:inline></script>
|
<script src="/js/bootstrap.min.js" is:inline></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const api = {
|
|||||||
builds: {
|
builds: {
|
||||||
v1: {
|
v1: {
|
||||||
async getLatestVersions(): Promise<ApiResult<LatestBuildsResponse>> {
|
async getLatestVersions(): Promise<ApiResult<LatestBuildsResponse>> {
|
||||||
|
try {
|
||||||
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/latest`, {
|
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/latest`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -27,8 +28,15 @@ export const api = {
|
|||||||
var obj = (await response.json()) as LatestBuildsResponse;
|
var obj = (await response.json()) as LatestBuildsResponse;
|
||||||
|
|
||||||
return ApiResult.success(obj);
|
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.");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getPatchNotes(): Promise<ApiResult<PatchNote[]>> {
|
async getPatchNotes(): Promise<PatchNote[]> {
|
||||||
|
try {
|
||||||
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/patchNotes`, {
|
const response = await fetch(`${TWY_API_URL}/${BUILDS_V1}/patchNotes`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -41,12 +49,14 @@ export const api = {
|
|||||||
status: response.status,
|
status: response.status,
|
||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
});
|
});
|
||||||
return ApiResult.failure<PatchNote[]>("Failed to load latest versions.");
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = (await response.json()) as PatchNote[];
|
return (await response.json()) as PatchNote[];
|
||||||
|
|
||||||
return ApiResult.success(obj);
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/lib/downloads.ts
Normal file
66
src/lib/downloads.ts
Normal 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));
|
||||||
|
};
|
||||||
@@ -1,40 +1,12 @@
|
|||||||
---
|
---
|
||||||
|
import { api } from "../lib/api";
|
||||||
|
import { getLatestDownloads } from "../lib/downloads";
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
|
||||||
const downloads = [
|
const latestDownloads = await getLatestDownloads();
|
||||||
{
|
const downloads = latestDownloads.success ? latestDownloads.data : [];
|
||||||
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 patchNotes = [
|
const patchNotes = await api.builds.v1.getPatchNotes();
|
||||||
{
|
|
||||||
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.",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
@@ -47,6 +19,8 @@ const patchNotes = [
|
|||||||
Choose the build for your platform.
|
Choose the build for your platform.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{
|
||||||
|
downloads.length > 0 ? (
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{downloads.map((download) => (
|
{downloads.map((download) => (
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
@@ -59,16 +33,13 @@ const patchNotes = [
|
|||||||
height="72"
|
height="72"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<h3>{download.platform}</h3>
|
<h3>{download.label}</h3>
|
||||||
<p>{download.version}</p>
|
<p>{download.version}</p>
|
||||||
{/* <p class="muted">
|
|
||||||
<code>{download.checksum}</code>
|
|
||||||
</p> */}
|
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href={download.href}
|
href={download.href}
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
aria-label={`Download for ${download.platform}`}
|
aria-label={`Download for ${download.label}`}
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
@@ -77,12 +48,20 @@ const patchNotes = [
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Downloads are unavailable right now.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<section class="download-patch-notes">
|
<section class="download-patch-notes">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Patch Notes</h2>
|
<h2>Patch Notes</h2>
|
||||||
</div>
|
</div>
|
||||||
{patchNotes.map((patchNote) => (
|
{
|
||||||
|
patchNotes.length > 0 ? (
|
||||||
|
patchNotes.map((patchNote) => (
|
||||||
<div class="well patch-note">
|
<div class="well patch-note">
|
||||||
<h3>{patchNote.version}</h3>
|
<h3>{patchNote.version}</h3>
|
||||||
<p class="muted">{patchNote.date}</p>
|
<p class="muted">{patchNote.date}</p>
|
||||||
@@ -92,7 +71,13 @@ const patchNotes = [
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<div class="well patch-note">
|
||||||
|
<p>No patch notes yet.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import DownloadButton from "../components/DownloadButton.astro";
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
|
||||||
const credits = [
|
const credits = [
|
||||||
@@ -105,17 +106,7 @@ const credits = [
|
|||||||
/>
|
/>
|
||||||
<h1 class="masthead-title">TenWholeYears</h1>
|
<h1 class="masthead-title">TenWholeYears</h1>
|
||||||
<p>A love letter to Rec Room on its true 10th anniversary</p>
|
<p>A love letter to Rec Room on its true 10th anniversary</p>
|
||||||
<div class="masthead-download">
|
<DownloadButton />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
Reference in New Issue
Block a user