Skip to content

Commit 2b88c5c

Browse files
authoredJan 16, 2025··
feat: Add Download Page (#105)
Add a download page to easily allow downloading the latest release.
1 parent 3370faa commit 2b88c5c

11 files changed

+326
-10
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ node_modules
1111
dist
1212
dist-ssr
1313
*.local
14+
src/data
1415

1516
# Editor directories and files
1617
.vscode/*

‎download.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/zmk.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>ZMK Studio - Download</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/download.tsx"></script>
12+
</body>
13+
</html>

‎package-lock.json

+79-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
"version": "0.2.4",
55
"type": "module",
66
"scripts": {
7-
"dev": "vite",
8-
"build": "tsc && vite build",
7+
"generate-data": "node scripts/generate-release-data.js",
8+
"dev": "npm run generate-data && vite",
9+
"build": "npm run generate-data && tsc && vite build",
910
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1011
"preview": "vite preview",
1112
"tauri": "tauri",
1213
"storybook": "storybook dev -p 6006",
1314
"build-storybook": "storybook build"
1415
},
1516
"dependencies": {
17+
"@fortawesome/fontawesome-svg-core": "^6.7.1",
18+
"@fortawesome/free-brands-svg-icons": "^6.7.1",
19+
"@fortawesome/react-fontawesome": "^0.2.2",
1620
"@tailwindcss/container-queries": "^0.1.1",
1721
"@tauri-apps/api": "^2.0.0",
1822
"@tauri-apps/plugin-cli": "^2.0.0",
@@ -60,4 +64,4 @@
6064
"@esbuild/darwin-arm64": "0.23.0",
6165
"@rollup/rollup-darwin-arm64": "^4.18.1"
6266
}
63-
}
67+
}

‎public/zmk-mac-app-icon.webp

22.5 KB
Binary file not shown.

‎scripts/generate-release-data.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import fs from "fs/promises";
2+
import path from "path";
3+
import url from "url";
4+
5+
const __filename = url.fileURLToPath(import.meta.url);
6+
const __dirname = path.resolve(__filename, "../..");
7+
8+
async function generateReleaseData() {
9+
try {
10+
const response = await fetch(
11+
"https://api.github.com/repos/zmkfirmware/zmk-studio/releases/latest",
12+
);
13+
if (!response.ok) {
14+
throw new Error(`HTTP error! status: ${response.status}`);
15+
}
16+
17+
const data = await response.json();
18+
const dataFilePath = path.resolve(__dirname, "src", "data", "release-data.json");
19+
await fs.mkdir(path.dirname(dataFilePath), { recursive: true });
20+
await fs.writeFile(dataFilePath, JSON.stringify(data));
21+
22+
console.log("Release data generated successfully!");
23+
} catch (error) {
24+
console.error("Error generating release data:", error);
25+
process.exit(1);
26+
}
27+
}
28+
29+
generateReleaseData();

‎src/ConnectModal.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ function noTransportsOptionsPrompt() {
247247
</li>
248248
<li>
249249
Download our{" "}
250-
<ExternalLink href="https://github.com/zmkfirmware/zmk-studio/releases">
250+
<ExternalLink href="/download">
251251
cross platform application
252252
</ExternalLink>
253253
.

‎src/DownloadPage.tsx

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { useEffect, useState } from "react";
2+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3+
import {
4+
faAndroid,
5+
faApple,
6+
faLinux,
7+
faWindows,
8+
IconDefinition,
9+
} from "@fortawesome/free-brands-svg-icons";
10+
import { DownloadIcon } from "lucide-react";
11+
import releaseData from "./data/release-data.json";
12+
13+
type Platform = "windows" | "mac" | "linux" | "ios" | "android" | "unknown";
14+
15+
const PlatformMetadata: Record<
16+
Platform,
17+
{ name: string; icon: IconDefinition }
18+
> = {
19+
windows: {
20+
name: "Windows",
21+
icon: faWindows,
22+
},
23+
mac: {
24+
name: "macOS",
25+
icon: faApple,
26+
},
27+
linux: {
28+
name: "Linux",
29+
icon: faLinux,
30+
},
31+
ios: {
32+
name: "iOS",
33+
icon: faApple,
34+
},
35+
android: {
36+
name: "Android",
37+
icon: faAndroid,
38+
},
39+
unknown: {
40+
name: "Unknown",
41+
icon: faAndroid,
42+
},
43+
};
44+
45+
type DownloadLink = {
46+
name: string;
47+
urlPattern: RegExp;
48+
};
49+
50+
const DownloadLinks: Record<string, DownloadLink> = {
51+
windows_exe: {
52+
name: "Windows (exe)",
53+
urlPattern: /.*\.exe/,
54+
},
55+
windows_msi: {
56+
name: "Windows (msi)",
57+
urlPattern: /.*\.msi/,
58+
},
59+
macos: {
60+
name: "macOS",
61+
urlPattern: /.*\.dmg/,
62+
},
63+
linux_appimage: {
64+
name: "Linux (AppImage)",
65+
urlPattern: /.*\.AppImage/,
66+
},
67+
linux_deb: {
68+
name: "Linux (deb)",
69+
urlPattern: /.*\.deb/,
70+
},
71+
};
72+
73+
const PlatformLinks: Record<Platform, DownloadLink[]> = {
74+
windows: [DownloadLinks.windows_exe, DownloadLinks.windows_msi],
75+
mac: [DownloadLinks.macos],
76+
linux: [DownloadLinks.linux_appimage, DownloadLinks.linux_deb],
77+
ios: [],
78+
android: [],
79+
unknown: [],
80+
};
81+
82+
const ReleaseAssets = releaseData.assets.map((asset: any) => asset.browser_download_url);
83+
const ReleaseVersion = releaseData.tag_name;
84+
85+
function detectPlatform(): Platform {
86+
if (typeof window === "undefined") return "unknown";
87+
88+
const userAgent = window.navigator.userAgent.toLowerCase();
89+
90+
if (userAgent.includes("win")) return "windows";
91+
if (userAgent.includes("mac")) return "mac";
92+
if (userAgent.includes("linux")) return "linux";
93+
if (/iphone|ipad|ipod/.test(userAgent)) return "ios";
94+
if (userAgent.includes("android")) return "android";
95+
96+
return "unknown";
97+
}
98+
99+
function getUrlFromPattern(assets: string[], pattern: RegExp) {
100+
const asset = assets.find((asset) => pattern.test(asset));
101+
return asset;
102+
}
103+
104+
export const Download = () => {
105+
const [platform, setPlatform] = useState<Platform>("unknown");
106+
const [showAll, setShowAll] = useState(false);
107+
108+
useEffect(() => {
109+
const platform = detectPlatform();
110+
setPlatform(platform);
111+
if (PlatformLinks[platform].length === 0) {
112+
setShowAll(true);
113+
}
114+
}, []);
115+
116+
return (
117+
<div className="bg-base-200 dark:bg-base-300 text-base-content min-h-full w-full flex flex-col justify-center items-center p-10 pb-48">
118+
<img src="/zmk-mac-app-icon.webp" alt="ZMK Studio" className="w-64" />
119+
<div className="text-3xl mb-1">ZMK Studio</div>
120+
<div className="text-md mb-1 opacity-70">
121+
{ReleaseVersion}
122+
</div>
123+
<div className="bg-base-100 p-8 max-w-md w-full m-2 rounded-lg shadow-lg dark:shadow-xl">
124+
{PlatformLinks[platform].length > 0 && (
125+
<>
126+
<div className="flex flex-col gap-3 mb-3">
127+
{PlatformLinks[platform].map((link) => (
128+
<a
129+
key={link.name}
130+
href={getUrlFromPattern(ReleaseAssets, link.urlPattern)}
131+
className="p-3 text-lg bg-primary hover:opacity-85 active:opacity-70 text-primary-content rounded-lg justify-center items-center gap-3 flex"
132+
>
133+
<FontAwesomeIcon icon={PlatformMetadata[platform].icon} className="h-6"/>{" "}
134+
Download for {link.name}
135+
</a>
136+
))}
137+
</div>
138+
</>
139+
)}
140+
<div className="flex flex-col gap-3">
141+
{PlatformLinks[platform].length > 0 && (
142+
<button
143+
onClick={() => setShowAll(!showAll)}
144+
className="text-primary text-left hover:underline"
145+
>
146+
{showAll ? "Hide" : "Show"} all downloads
147+
</button>
148+
)}
149+
{showAll && (
150+
<div>
151+
{Object.entries(PlatformLinks).map(([platform, links]) => (
152+
<div key={platform}>
153+
{links.map((link) => (
154+
<a
155+
key={link.name}
156+
href={getUrlFromPattern(ReleaseAssets, link.urlPattern)}
157+
className="flex gap-1 mb-3 text-base-content hover:underline"
158+
>
159+
<DownloadIcon className="w-5" />
160+
{link.name}
161+
</a>
162+
))}
163+
</div>
164+
))}
165+
</div>
166+
)}
167+
</div>
168+
</div>
169+
<a
170+
className="text-md hover:underline"
171+
href="https://github.com/zmkfirmware/zmk-studio/releases"
172+
>
173+
See GitHub Releases →
174+
</a>
175+
</div>
176+
);
177+
};

‎src/download.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom/client";
3+
import "./index.css";
4+
5+
import { Download } from "./DownloadPage";
6+
7+
ReactDOM.createRoot(document.getElementById("root")!).render(
8+
<React.StrictMode>
9+
<Download />
10+
</React.StrictMode>,
11+
);

‎tailwind.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import trac from "tailwindcss-react-aria-components";
33
import contQueries from "@tailwindcss/container-queries";
44

55
export default {
6-
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
6+
content: ["./index.html", "./download.html", "./src/**/*.{js,ts,jsx,tsx}"],
77
theme: {
88
extend: {
99
fontFamily: {

‎vite.config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,12 @@ export default defineConfig({
2727
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
2828
// produce sourcemaps for debug builds
2929
sourcemap: !!process.env.TAURI_DEBUG,
30+
// include download page
31+
rollupOptions: {
32+
input: {
33+
main: "./index.html",
34+
download: "./download.html",
35+
},
36+
}
3037
},
3138
});

0 commit comments

Comments
 (0)
Please sign in to comment.