mirror of
https://github.com/Xevion/dynamic-preauth.git
synced 2025-12-05 23:14:53 -06:00
DownloadButton progress
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
"@astrojs/react": "^4.1.2",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrojs/tailwind": "^5.1.4",
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
|
||||
157
frontend/pnpm-lock.yaml
generated
157
frontend/pnpm-lock.yaml
generated
@@ -17,6 +17,12 @@ importers:
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4(astro@5.1.1(jiti@2.4.2)(rollup@4.29.1)(sass-embedded@1.83.0)(typescript@5.7.2)(yaml@2.6.1))(tailwindcss@3.4.17)
|
||||
'@headlessui/react':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@heroicons/react':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(react@19.0.0)
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.15
|
||||
version: 0.5.15(tailwindcss@3.4.17)
|
||||
@@ -470,9 +476,33 @@ packages:
|
||||
'@floating-ui/dom@1.6.12':
|
||||
resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.2':
|
||||
resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/react@0.26.28':
|
||||
resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.8':
|
||||
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
||||
|
||||
'@headlessui/react@2.2.0':
|
||||
resolution: {integrity: sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||
|
||||
'@heroicons/react@2.2.0':
|
||||
resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
|
||||
peerDependencies:
|
||||
react: '>= 16 || ^19.0.0-rc'
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
@@ -707,6 +737,37 @@ packages:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@react-aria/focus@3.19.0':
|
||||
resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-aria/interactions@3.22.5':
|
||||
resolution: {integrity: sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-aria/ssr@3.9.7':
|
||||
resolution: {integrity: sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==}
|
||||
engines: {node: '>= 12'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-aria/utils@3.26.0':
|
||||
resolution: {integrity: sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-stately/utils@3.10.5':
|
||||
resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-types/shared@3.26.0':
|
||||
resolution: {integrity: sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@rollup/pluginutils@5.1.4':
|
||||
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -826,11 +887,23 @@ packages:
|
||||
'@shikijs/vscode-textmate@9.3.1':
|
||||
resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
'@tailwindcss/typography@0.5.15':
|
||||
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
|
||||
|
||||
'@tanstack/react-virtual@3.11.2':
|
||||
resolution: {integrity: sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@tanstack/virtual-core@3.11.2':
|
||||
resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -2213,6 +2286,9 @@ packages:
|
||||
resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@2.5.5:
|
||||
resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==}
|
||||
|
||||
@@ -2878,8 +2954,35 @@ snapshots:
|
||||
'@floating-ui/core': 1.6.8
|
||||
'@floating-ui/utils': 0.2.8
|
||||
|
||||
'@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.6.12
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@floating-ui/react@0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@floating-ui/utils': 0.2.8
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
tabbable: 6.2.0
|
||||
|
||||
'@floating-ui/utils@0.2.8': {}
|
||||
|
||||
'@headlessui/react@2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@react-aria/focus': 3.19.0(react@19.0.0)
|
||||
'@react-aria/interactions': 3.22.5(react@19.0.0)
|
||||
'@tanstack/react-virtual': 3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@heroicons/react@2.2.0(react@19.0.0)':
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.0.4
|
||||
@@ -3063,6 +3166,46 @@ snapshots:
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@react-aria/focus@3.19.0(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.22.5(react@19.0.0)
|
||||
'@react-aria/utils': 3.26.0(react@19.0.0)
|
||||
'@react-types/shared': 3.26.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.15
|
||||
clsx: 2.1.1
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/interactions@3.22.5(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.7(react@19.0.0)
|
||||
'@react-aria/utils': 3.26.0(react@19.0.0)
|
||||
'@react-types/shared': 3.26.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.15
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/ssr@3.9.7(react@19.0.0)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.15
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/utils@3.26.0(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.7(react@19.0.0)
|
||||
'@react-stately/utils': 3.10.5(react@19.0.0)
|
||||
'@react-types/shared': 3.26.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.15
|
||||
clsx: 2.1.1
|
||||
react: 19.0.0
|
||||
|
||||
'@react-stately/utils@3.10.5(react@19.0.0)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.15
|
||||
react: 19.0.0
|
||||
|
||||
'@react-types/shared@3.26.0(react@19.0.0)':
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
'@rollup/pluginutils@5.1.4(rollup@4.29.1)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
@@ -3155,6 +3298,10 @@ snapshots:
|
||||
|
||||
'@shikijs/vscode-textmate@9.3.1': {}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.17)':
|
||||
dependencies:
|
||||
lodash.castarray: 4.4.0
|
||||
@@ -3163,6 +3310,14 @@ snapshots:
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.4.17
|
||||
|
||||
'@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.11.2
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@tanstack/virtual-core@3.11.2': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.3
|
||||
@@ -4892,6 +5047,8 @@ snapshots:
|
||||
|
||||
system-architecture@0.1.0: {}
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@2.5.5: {}
|
||||
|
||||
tailwindcss@3.4.17:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Badge from "@/components/Badge";
|
||||
import DownloadButton from "@/components/DownloadButton";
|
||||
import Emboldened from "@/components/Emboldened";
|
||||
import useSocket from "@/components/useSocket";
|
||||
import { cn, plural, toHex, type ClassValue } from "@/util";
|
||||
@@ -9,9 +10,11 @@ type DemoProps = {
|
||||
};
|
||||
|
||||
const Demo = ({ class: className }: DemoProps) => {
|
||||
const { id, downloads } = useSocket();
|
||||
const { id, downloads, executables } = useSocket();
|
||||
// TODO: Toasts
|
||||
|
||||
console.log([executables == null]);
|
||||
|
||||
const [highlightedIndex, setHighlightedIndex] = useState<number | null>(null);
|
||||
const highlightedTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
@@ -29,8 +32,8 @@ const Demo = ({ class: className }: DemoProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={cn(className, "px-5 leading-6")}>
|
||||
<p class="mt-3 mb-3">
|
||||
<div className={cn(className, "px-5 leading-6")}>
|
||||
<p className="mt-3 mb-3">
|
||||
This demo uses websockets to communicate between the server and the
|
||||
browser. Each download gets a unique identifier bound to the user
|
||||
session.
|
||||
@@ -45,7 +48,12 @@ const Demo = ({ class: className }: DemoProps) => {
|
||||
</Emboldened>{" "}
|
||||
known {plural("download", downloads?.length ?? 0)}.
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-y-2.5">
|
||||
<div className="flex flex-wrap justify-center gap-y-2.5">
|
||||
<DownloadButton
|
||||
disabled={executables == null}
|
||||
buildLog={"https://railway.com"}
|
||||
executables={executables}
|
||||
/>
|
||||
{downloads?.map((download, i) => (
|
||||
<Badge
|
||||
className={cn(
|
||||
@@ -65,8 +73,8 @@ const Demo = ({ class: className }: DemoProps) => {
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div class="mt-4 p-2 bg-zinc-900/90 rounded-md border border-zinc-700">
|
||||
<p class="my-0">
|
||||
<div className="mt-4 p-2 bg-zinc-900/90 rounded-md border border-zinc-700">
|
||||
<p className="my-0">
|
||||
The server running this is completely ephemeral, can restart at any
|
||||
time, and purges data on regular intervals - at which point the
|
||||
executables you've downloaded will no longer function.
|
||||
|
||||
177
frontend/src/components/DownloadButton.tsx
Normal file
177
frontend/src/components/DownloadButton.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import type { Executable } from "@/components/useSocket";
|
||||
import { cn, withBackend } from "@/util";
|
||||
import {
|
||||
Button,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
MenuSeparator,
|
||||
} from "@headlessui/react";
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
BeakerIcon,
|
||||
ChevronDownIcon,
|
||||
} from "@heroicons/react/16/solid";
|
||||
import { useRef } from "react";
|
||||
|
||||
type DownloadButtonProps = {
|
||||
disabled?: boolean;
|
||||
executables: Executable[] | null;
|
||||
buildLog: string | null;
|
||||
};
|
||||
|
||||
type SystemType = "windows" | "macos" | "linux";
|
||||
|
||||
function getSystemType(): SystemType | null {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes("win")) {
|
||||
return "windows";
|
||||
} else if (userAgent.includes("mac")) {
|
||||
return "macos";
|
||||
} else if (userAgent.includes("linux")) {
|
||||
return "linux";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function DownloadButton({
|
||||
disabled,
|
||||
executables,
|
||||
buildLog,
|
||||
}: DownloadButtonProps) {
|
||||
const menuRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
console.log({ disabled });
|
||||
|
||||
function getExecutable(id: string) {
|
||||
return executables?.find((e) => e.id.toLowerCase() === id.toLowerCase());
|
||||
}
|
||||
|
||||
async function handleDownload(id: string) {
|
||||
const executable = getExecutable(id);
|
||||
if (executable == null) {
|
||||
console.error(`Executable ${id} not found, cannot download`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(withBackend(`/download/${executable.id}`), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.headers.get("Content-Type") === "application/json") {
|
||||
const json = await response.json();
|
||||
console.error("Download failed", json);
|
||||
} else {
|
||||
console.error(
|
||||
"Download failed (unreadable response)",
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create blob link to download
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// Create a link element and click it to download the file
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", executable.filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.parentNode!.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Download failed", error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDownloadAutomatic() {
|
||||
const systemType = getSystemType();
|
||||
|
||||
// If the system type is unknown/unavailable, open the menu for manual selection
|
||||
if (systemType == null || getExecutable(systemType) == null) {
|
||||
menuRef.current?.click();
|
||||
}
|
||||
|
||||
// Otherwise, download the executable automatically
|
||||
else {
|
||||
handleDownload(systemType);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"[&>*]:py-1 overflow-clip transition-[background-color] text-sm/6 flex items-center shadow-inner align-middle text-white focus:outline-none data-[focus]:outline-1 data-[focus]:outline-white",
|
||||
!disabled
|
||||
? "divide-white/[0.2] shadow-white/10 bg-emerald-800 data-[hover]:bg-emerald-700 data-[open]:bg-emerald-700"
|
||||
: "divide-white/[0.1] shadow-white/5 animate-pulse-dark data-[hover]:bg-[#064e3b] cursor-wait",
|
||||
"rounded-md divide-x h-full rounded-l-md"
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
onClick={handleDownloadAutomatic}
|
||||
disabled={disabled}
|
||||
className={cn("pl-3 font-semibold pr-2.5", {
|
||||
"hover:bg-white/5": !disabled,
|
||||
})}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
ref={menuRef}
|
||||
disabled={disabled ?? false}
|
||||
className={cn("pl-1.5 text-transparent min-h-8 pr-2", {
|
||||
"hover:bg-white/5": !disabled,
|
||||
})}
|
||||
>
|
||||
<ChevronDownIcon className="size-4 fill-white/60" />
|
||||
</MenuButton>
|
||||
<MenuItems
|
||||
transition
|
||||
anchor="bottom end"
|
||||
className="w-40 z-20 mt-1 origin-top-right rounded-xl border border-white/[0.08] bg-zinc-900 shadow-md p-1 text-sm/6 text-zinc-200 transition duration-100 ease-out [--anchor-gap:var(--spacing-1)] focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0"
|
||||
>
|
||||
{executables?.map((executable) => (
|
||||
<MenuItem key={executable.id}>
|
||||
<button
|
||||
className="group flex w-full items-center justify-between gap-2 rounded-lg py-1.5 pl-2 pr-2.5 data-[focus]:bg-white/10"
|
||||
onClick={() => handleDownload(executable.id)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ArrowDownTrayIcon className="size-4 fill-white/40" />
|
||||
{executable.id}
|
||||
</div>
|
||||
<div className="text-xs text-zinc-500">
|
||||
{(executable.size / 1024 / 1024).toFixed(1)} MiB
|
||||
</div>
|
||||
</button>
|
||||
</MenuItem>
|
||||
))}
|
||||
{buildLog != null ? (
|
||||
<>
|
||||
<MenuSeparator className="my-1 h-px bg-white/10" />
|
||||
<MenuItem>
|
||||
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-2 data-[focus]:bg-white/10">
|
||||
<BeakerIcon className="size-4 fill-white/40" />
|
||||
Build Logs
|
||||
</button>
|
||||
</MenuItem>
|
||||
</>
|
||||
) : null}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,19 @@ export default {
|
||||
content: ["./src/**/*.{astro,html,js,jsx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
"pulse-dark": "pulse-dark 2.5s ease-in-out infinite",
|
||||
},
|
||||
keyframes: {
|
||||
"pulse-dark": {
|
||||
"0%, 100%": {
|
||||
backgroundColor: "#0A3026",
|
||||
},
|
||||
"50%": {
|
||||
backgroundColor: "#053B2D",
|
||||
},
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
bebas: ["Bebas Neue", "sans-serif"],
|
||||
inter: ["Inter", "sans-serif"],
|
||||
|
||||
Reference in New Issue
Block a user