feat: Added plugins section and made it more mobile friendly
All checks were successful
Build Gameflow Site / build (push) Successful in 1m19s
All checks were successful
Build Gameflow Site / build (push) Successful in 1m19s
This commit is contained in:
parent
13f1d97394
commit
87f8f485aa
26 changed files with 331 additions and 63 deletions
15
package.json
15
package.json
|
|
@ -33,7 +33,20 @@
|
|||
"cross-platform",
|
||||
"gamepad",
|
||||
"controller",
|
||||
"ux"
|
||||
"ux",
|
||||
"plugins",
|
||||
"community",
|
||||
"cloud",
|
||||
"emulators",
|
||||
"steam-deck",
|
||||
"linux",
|
||||
"windows",
|
||||
"app",
|
||||
"webview",
|
||||
"bun",
|
||||
"es-de",
|
||||
"store",
|
||||
"free"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
|
|
|
|||
3
src/assets/screenshots/3dscreenshot.json
Normal file
3
src/assets/screenshots/3dscreenshot.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Gameflow 3D Screenshow hero image"
|
||||
}
|
||||
3
src/assets/screenshots/3nhuKCK6E3.json
Normal file
3
src/assets/screenshots/3nhuKCK6E3.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Game details screen of the app. Showing the rom Peter Jackson's King Kong Game in dark mode."
|
||||
}
|
||||
3
src/assets/screenshots/4MtAe7Wkev.json
Normal file
3
src/assets/screenshots/4MtAe7Wkev.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the responsive layout and the experimental portrait layout in specific."
|
||||
}
|
||||
3
src/assets/screenshots/6wz3gW8c2h.json
Normal file
3
src/assets/screenshots/6wz3gW8c2h.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Game details screen of the app. Showing the rom Peter Jackson's King Kong Game in light mode."
|
||||
}
|
||||
3
src/assets/screenshots/CpBLzTNM6N.json
Normal file
3
src/assets/screenshots/CpBLzTNM6N.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"atl": "The free in app store home page. Showing recommended free games and emulators."
|
||||
}
|
||||
3
src/assets/screenshots/EWPHmIBEE5.json
Normal file
3
src/assets/screenshots/EWPHmIBEE5.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the gameflow platform list. Where users can find games based on platforms. Like Xbox or Playstation"
|
||||
}
|
||||
3
src/assets/screenshots/GL7SkQbHIY.json
Normal file
3
src/assets/screenshots/GL7SkQbHIY.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the plugins page for the gameflow app. It shows internal plugins built into the app. Like ES-DE autoconfig or different emulator implementations. All this in dark mode."
|
||||
}
|
||||
3
src/assets/screenshots/MMeJxl4IXr.json
Normal file
3
src/assets/screenshots/MMeJxl4IXr.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing all emulator for download that will be fully supported by gameflow. Where you can easily install emulators and download firmware if you have romm setup. All this in light mode."
|
||||
}
|
||||
3
src/assets/screenshots/Pkazk0RufB.json
Normal file
3
src/assets/screenshots/Pkazk0RufB.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the homescreen of the app. The default filter is most activity. Be it last played or freshly installed. In light mode."
|
||||
}
|
||||
3
src/assets/screenshots/rBY2mgTLy0.json
Normal file
3
src/assets/screenshots/rBY2mgTLy0.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the plugins page for the gameflow app. It shows internal plugins built into the app. Like ES-DE autoconfig or different emulator implementations. All this in light mode."
|
||||
}
|
||||
3
src/assets/screenshots/yObFD2LySH.json
Normal file
3
src/assets/screenshots/yObFD2LySH.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing the homescreen of the app. The default filter is most activity. Be it last played or freshly installed. In dark mode."
|
||||
}
|
||||
3
src/assets/screenshots/zEQxtzhPGx.json
Normal file
3
src/assets/screenshots/zEQxtzhPGx.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"alt": "Showing all emulator for download that will be fully supported by gameflow. Where you can easily install emulators and download firmware if you have romm setup. All this in dark mode."
|
||||
}
|
||||
35
src/components/Dock.astro
Normal file
35
src/components/Dock.astro
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import { HandCoins, House, Puzzle } from "@lucide/astro";
|
||||
import { Github } from "simple-icons-astro";
|
||||
import pkg from "../../package.json";
|
||||
---
|
||||
|
||||
<div class="md:hidden dock dock-sm" style="view-transition-name: footer;">
|
||||
<a
|
||||
data-active={Astro.url.pathname === `${import.meta.env.BASE_URL}/`}
|
||||
class="data-[active=true]:dock-active"
|
||||
href={import.meta.env.BASE_URL}
|
||||
>
|
||||
<House />
|
||||
<span class="dock-label">Home</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
data-active={Astro.url.pathname.startsWith(
|
||||
`${import.meta.env.BASE_URL}/plugins`,
|
||||
)}
|
||||
class="data-[active=true]:dock-active"
|
||||
href={`${import.meta.env.BASE_URL}plugins`}
|
||||
>
|
||||
<Puzzle />
|
||||
<span class="dock-label">Plugins</span>
|
||||
</a>
|
||||
<a href={pkg.socials.github}>
|
||||
<Github />
|
||||
<span class="dock-label">Github</span>
|
||||
</a>
|
||||
<a href={pkg.sponsor.url}>
|
||||
<HandCoins />
|
||||
<span class="dock-label">Sponsor</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
const { title, description, icon } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="flex gap-4 justify-center m-4 p-4 ring-2 ring-accent/5 rounded-2xl">
|
||||
<div class="flex gap-4 m-4 p-4 ring-2 ring-accent/5 rounded-2xl">
|
||||
<slot name="icon" />
|
||||
<div class="flex flex-col">
|
||||
<h1>{title}</h1>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
Store,
|
||||
RefreshCcw,
|
||||
Joystick,
|
||||
Puzzle,
|
||||
} from "@lucide/astro";
|
||||
import Feature from "./Feature.astro";
|
||||
---
|
||||
|
|
@ -76,4 +77,10 @@ import Feature from "./Feature.astro";
|
|||
>
|
||||
<SunMoon size={64} slot="icon" />
|
||||
</Feature>
|
||||
<Feature
|
||||
title="Plugins System"
|
||||
description="Extent functionality with plugins. Hosted on the NPM registry."
|
||||
>
|
||||
<Puzzle size={64} slot="icon" />
|
||||
</Feature>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import { Github, Youtube } from "simple-icons-astro";
|
||||
import { Discord, Github, Youtube } from "simple-icons-astro";
|
||||
import { HandCoins } from "@lucide/astro";
|
||||
import pkg from "../../package.json";
|
||||
---
|
||||
|
|
@ -11,11 +11,17 @@ import pkg from "../../package.json";
|
|||
<a class="link link-hover" href={pkg.downloads}>Downloads</a>
|
||||
<a class="link link-hover" href={pkg.socials.mirror}>Mirror</a>
|
||||
<a class="link link-hover" href={pkg.socials.license}>License</a>
|
||||
<a class="link link-hover" href={pkg.socials.packages}>Packages</a>
|
||||
</nav>
|
||||
<nav>
|
||||
<h6 class="footer-title">Navigation</h6>
|
||||
<a class="link link-hover" href={`${import.meta.env.BASE_URL}plugins`}
|
||||
>Plugins</a
|
||||
>
|
||||
</nav>
|
||||
<nav>
|
||||
<h6 class="footer-title">Others</h6>
|
||||
<a class="link link-hover" href={pkg.socials.aboutus}>About us</a>
|
||||
<a class="link link-hover" href={pkg.socials.packages}>Packages</a>
|
||||
<a class="link link-hover" href={pkg.socials.store}>Store</a>
|
||||
<a class="link link-hover" href={pkg.socials.discord}>Discord</a>
|
||||
<a class="link link-hover" href={pkg.sponsor.url}>Sponsor</a>
|
||||
|
|
@ -32,6 +38,9 @@ import pkg from "../../package.json";
|
|||
<a title="Sponsor" href={pkg.sponsor.url}>
|
||||
<HandCoins />
|
||||
</a>
|
||||
<a title="Discord" href={pkg.socials.discord}>
|
||||
<Discord />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,52 @@
|
|||
---
|
||||
import { Github, Discord } from "simple-icons-astro";
|
||||
import { HandCoins } from "@lucide/astro";
|
||||
import { HandCoins, Puzzle } from "@lucide/astro";
|
||||
import Icon from "../assets/icon.svg";
|
||||
import pkg from "../../package.json";
|
||||
import { releaseData } from "../scripts/getters";
|
||||
import { plugins, releaseData } from "../scripts/getters";
|
||||
import Image from "astro/components/Image.astro";
|
||||
---
|
||||
|
||||
<div class="sticky flex top-0 navbar z-100 bg-base-200 justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<img class="size-12" src={Icon.src} />
|
||||
<div
|
||||
class="sticky flex flex-wrap gap-2 top-0 navbar z-100 bg-base-200 justify-between"
|
||||
style="view-transition-name: header;"
|
||||
>
|
||||
<a class="flex gap-2 items-center" href={import.meta.env.BASE_URL}>
|
||||
<Image src={Icon} class="size-12" alt="Gameflow Hero Screenshot" />
|
||||
<div class="text-primary font-bold text-lg uppercase">
|
||||
{pkg.displayName}
|
||||
</div>
|
||||
<div>Deck</div>
|
||||
<div class="bg-base-300 p-1 px-3 rounded-full">{releaseData.tag_name}</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
</a>
|
||||
<div class="hidden md:flex gap-2">
|
||||
<a
|
||||
title="View a list of plugins you can download for gameflow"
|
||||
data-active={Astro.url.pathname.startsWith(
|
||||
`${import.meta.env.BASE_URL}/plugins`,
|
||||
)}
|
||||
class="flex group gap-2 bg-base-300 data-[active=true]:cursor-default rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content data-[active=true]:bg-accent data-[active=true]:text-accent-content items-center"
|
||||
href={`${import.meta.env.BASE_URL}plugins`}
|
||||
><Puzzle />Plugins<span
|
||||
class="bg-base-100 px-2 text-sm font-semibold rounded-full group-hover:bg-accent-content group-hover:text-accent in-data-[active=true]:bg-accent-content in-data-[active=true]:text-accent-content"
|
||||
>{plugins.total}</span
|
||||
>
|
||||
</a>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<a
|
||||
title="Support Gameflow development"
|
||||
class="flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
|
||||
href={pkg.sponsor.url}
|
||||
><HandCoins />
|
||||
</a>
|
||||
<a
|
||||
class="hidden md:flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
|
||||
class="flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
|
||||
href={pkg.socials.discord}
|
||||
title="Join our Community on Discord"
|
||||
><Discord />
|
||||
</a>
|
||||
<a
|
||||
title="Sponsor"
|
||||
title="View source code on GitHub"
|
||||
class="hidden md:flex gap-2 bg-base-300 cursor-pointer rounded-full p-1 px-3 text-xl hover:bg-accent hover:text-accent-content"
|
||||
href={pkg.socials.github}
|
||||
><Github />GitHub
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
import screenshot from "../assets/screenshots/3dscreenshot.webp";
|
||||
import { Dot, Download } from "@lucide/astro";
|
||||
import { Dot, Download, Milestone, Scale } from "@lucide/astro";
|
||||
import { Github } from "simple-icons-astro";
|
||||
import pkg from "../../package.json";
|
||||
import { Image } from "astro:assets";
|
||||
import { releaseData } from "../scripts/getters";
|
||||
---
|
||||
|
||||
<div class="hero bg-base-100 py-16 md:p-0 md:min-h-[calc(100vh-19rem)]">
|
||||
|
|
@ -17,9 +18,13 @@ import { Image } from "astro:assets";
|
|||
<small
|
||||
class="flex text-base-content/40 gap-2 justify-center md:justify-start items-center mb-2"
|
||||
>
|
||||
<Github size={16} /> Open Source <Dot />
|
||||
<Github size={16} /> Open Source
|
||||
<Dot />
|
||||
<Scale />
|
||||
{pkg.license}
|
||||
<div class="badge badge-ghost">beta</div>
|
||||
<Dot />
|
||||
<Milestone />
|
||||
{releaseData.tag_name}
|
||||
</small>
|
||||
<h1
|
||||
class="flex flex-wrap gap-4 text-5xl font-bold text-primary justify-center md:justify-start"
|
||||
|
|
@ -29,12 +34,14 @@ import { Image } from "astro:assets";
|
|||
</h1>
|
||||
<p class="py-6">
|
||||
A Cross-Platform open source Retro gaming frontend designed for handheld
|
||||
and controllers. Focused on building a simple user experience and
|
||||
intuitive UI as a curated community driven experience.
|
||||
and controllers. Focused on building a simple user experience and fluent
|
||||
and intuitive UI as a curated community driven experience. Download and
|
||||
play what you need and let the app do the rest.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 justify-center md:justify-start">
|
||||
<a
|
||||
class="bg-primary rounded-full text-primary-content p-2 px-4 hover:bg-secondary hover:text-secondary-content cursor-pointer flex gap-2"
|
||||
title="Download Gameflow from GitHub releases"
|
||||
href={pkg.downloads}
|
||||
target="_blank"
|
||||
>
|
||||
|
|
@ -43,6 +50,7 @@ import { Image } from "astro:assets";
|
|||
</a>
|
||||
<a
|
||||
class="bg-primary rounded-full text-primary-content p-2 px-4 hover:bg-accent hover:text-accent-content cursor-pointer flex gap-2"
|
||||
title="View Source code on GitHub. As well as issues"
|
||||
href={pkg.socials.github}
|
||||
target="_blank"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ import { Image } from "astro:assets";
|
|||
const images = import.meta.glob("../assets/screenshots/*.webp", {
|
||||
eager: true,
|
||||
});
|
||||
const metas = import.meta.glob("../assets/screenshots/*.json", {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const items = Object.entries(images).map(([imgPath, img]) => {
|
||||
const metaPath = imgPath.replace(/\.(jpg|jpeg|png|webp)$/, ".json");
|
||||
const meta = metas[metaPath]?.default ?? {};
|
||||
return { image: img.default, meta, path: imgPath };
|
||||
});
|
||||
---
|
||||
|
||||
<div class="divider sm:my-8 md:my-16"><ImageIcon size={48} />Screenshots</div>
|
||||
|
|
@ -11,15 +20,19 @@ const images = import.meta.glob("../assets/screenshots/*.webp", {
|
|||
class="columns-1 sm:columns-2 md:columns-3 lg:columns-4 gap-4 sm:p-8 md:p-16"
|
||||
>
|
||||
{
|
||||
Object.values(images).map((img: any, i) => (
|
||||
<a href={img.default.src} target="_blank">
|
||||
<Image
|
||||
class="mb-4 w-full rounded-xl"
|
||||
alt={`Screenshot ${i}`}
|
||||
src={img.default}
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
))
|
||||
items.map(({ image, meta }, i) => {
|
||||
const element = (
|
||||
<a href={image.src} target="_blank">
|
||||
<Image
|
||||
class="mb-4 w-full rounded-xl"
|
||||
alt={meta.alt ?? `Screenshot ${i}`}
|
||||
src={image}
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
return element;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
Star,
|
||||
GitCommitVertical,
|
||||
UserPen,
|
||||
Puzzle,
|
||||
} from "@lucide/astro";
|
||||
import {
|
||||
repoData,
|
||||
|
|
@ -12,6 +13,7 @@ import {
|
|||
appContributorsData,
|
||||
storeContributorsData,
|
||||
totalDownloads,
|
||||
plugins,
|
||||
} from "../scripts/getters";
|
||||
---
|
||||
|
||||
|
|
@ -21,34 +23,28 @@ import {
|
|||
<div class="flex gap-4">
|
||||
<Star class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span id="stars" class="text-2xl font-semibold"
|
||||
>{repoData.stargazers_count}+</span
|
||||
>
|
||||
<span class="text-2xl font-semibold">{repoData.stargazers_count}+</span>
|
||||
<div class="text-base-content/60">GitHub Stars</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<Download class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span id="downloads" class="text-2xl font-semibold"
|
||||
>{totalDownloads}+</span
|
||||
>
|
||||
<span class="text-2xl font-semibold">{totalDownloads}+</span>
|
||||
<div class="text-base-content/60">Downloads</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<Joystick class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span id="downloads" class="text-2xl font-semibold"
|
||||
>{emulators.length}</span
|
||||
>
|
||||
<span class="text-2xl font-semibold">{emulators.length}</span>
|
||||
<div class="text-base-content/60">Built In Emulators</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<GitCommitVertical class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span id="downloads" class="text-2xl font-semibold"
|
||||
<span class="text-2xl font-semibold"
|
||||
>{
|
||||
appContributorsData.reduce(
|
||||
(sum: number, user: any) => sum + user.contributions,
|
||||
|
|
@ -62,7 +58,7 @@ import {
|
|||
<div class="flex gap-4">
|
||||
<UserPen class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span id="downloads" class="text-2xl font-semibold"
|
||||
<span class="text-2xl font-semibold"
|
||||
>{
|
||||
new Set(
|
||||
appContributorsData
|
||||
|
|
@ -74,4 +70,11 @@ import {
|
|||
<div class="text-base-content/60">Contributors</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<Puzzle class="text-primary" size={48} />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-2xl font-semibold">{plugins.total}</span>
|
||||
<div class="text-base-content/60">Plugins</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import Features from "./Features.astro";
|
|||
import Community from "./Community.astro";
|
||||
---
|
||||
|
||||
<Header />
|
||||
<Hero />
|
||||
<Platforms />
|
||||
<Stats />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import "../assets/style.css";
|
|||
import favicon from "../assets/favicon.ico";
|
||||
import pkg from "../../package.json";
|
||||
import preview from "../assets/screenshots/3dscreenshot.webp";
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
import { getImage } from "astro:assets";
|
||||
const previewHref = new URL(
|
||||
(await getImage({ src: preview, format: "png" })).src,
|
||||
Astro.url,
|
||||
).href;
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -15,6 +17,7 @@ const previewHref = new URL(
|
|||
<head>
|
||||
<!-- Open Graph (Facebook, LinkedIn, Discord, Slack etc.) -->
|
||||
<meta property="og:title" content={`${pkg.displayName} Deck`} />
|
||||
<link rel="canonical" href={Astro.url.href} />
|
||||
<meta property="og:description" content={pkg.description} />
|
||||
<meta property="og:image" content={previewHref} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
|
|
@ -34,7 +37,8 @@ const previewHref = new URL(
|
|||
href="https://fonts.googleapis.com/css2?family=Alan+Sans:wght@300..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Gameflow Deck</title>
|
||||
<title>{title}</title>
|
||||
<ClientRouter />
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
---
|
||||
import Dock from "../components/Dock.astro";
|
||||
import Header from "../components/Header.astro";
|
||||
import Welcome from "../components/Welcome.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
|
||||
|
|
@ -6,6 +8,8 @@ import Layout from "../layouts/Layout.astro";
|
|||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Layout title="Gameflow Deck">
|
||||
<Header />
|
||||
<Welcome />
|
||||
<Dock />
|
||||
</Layout>
|
||||
|
|
|
|||
93
src/pages/plugins.astro
Normal file
93
src/pages/plugins.astro
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
import { Dot, Download, Puzzle } from "@lucide/astro";
|
||||
import Header from "../components/Header.astro";
|
||||
import Welcome from "../components/Welcome.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import { createHash } from "crypto"; // Node.js built-in
|
||||
import { plugins } from "../scripts/getters";
|
||||
import { Git, Npm } from "simple-icons-astro";
|
||||
import Dock from "../components/Dock.astro";
|
||||
|
||||
function getGravatarUrl(email: string, size = 80) {
|
||||
const hash = createHash("sha256")
|
||||
.update(email.trim().toLowerCase())
|
||||
.digest("hex");
|
||||
return `https://gravatar.com/avatar/${hash}?s=${size}`;
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title="Gameflow Deck - Plugins">
|
||||
<Header />
|
||||
<div class="divider">
|
||||
<Puzzle size={48} />Plugins <span
|
||||
class="bg-base-300 font-semibold px-2 rounded-2xl">{plugins.total}</span
|
||||
>
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<ul class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{
|
||||
plugins.objects.map((p) => {
|
||||
const plugins = p;
|
||||
const elements = [];
|
||||
if (p.package.publisher) {
|
||||
elements.push(
|
||||
<div class="flex gap-2">
|
||||
<img
|
||||
class="rounded-full size-6"
|
||||
src={getGravatarUrl(p.package.publisher?.email)}
|
||||
/>
|
||||
{p.package.publisher?.username}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
elements.push(p.package.version);
|
||||
if (p.package.license) elements.push(p.package.license);
|
||||
if (p.package.links.repository) {
|
||||
elements.push(
|
||||
<a title="Go to repository" href={p.package.links.repository}>
|
||||
<Git size={18} />
|
||||
</a>,
|
||||
);
|
||||
}
|
||||
if (p.package.links.npm) {
|
||||
elements.push(
|
||||
<a
|
||||
title={`Show ${p.package.name} on NPM`}
|
||||
href={p.package.links.npm}
|
||||
>
|
||||
<Npm size={16} />
|
||||
</a>,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li class="flex flex-wrap justify-between p-4 gap-2 bg-base-300 rounded-2xl">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<h1 class="font-bold text-2xl">{p.package.name}</h1>
|
||||
</div>
|
||||
<div class="text-base-content/60">{p.package.description}</div>
|
||||
<ul class="flex flex-wrap gap-2">
|
||||
{p.package.keywords?.map((k: string) => (
|
||||
<li class="bg-base-100 px-2 rounded-full">{k}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div class="flex gap-1 items-center">
|
||||
{elements.flatMap((item, i) =>
|
||||
i === 0 ? [item] : [<Dot />, item],
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="bg-base-100 px-4 py-2 flex gap-2 rounded-3xl">
|
||||
<Download />
|
||||
{p.downloads.monthly}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<Dock />
|
||||
</Layout>
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
const githubHeaders = import.meta.env.GITHUB_TOKEN
|
||||
? {
|
||||
headers: {
|
||||
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
}
|
||||
headers: {
|
||||
Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
export const repoData = await fetch(
|
||||
"https://api.github.com/repos/simeonradivoev/gameflow-deck",
|
||||
githubHeaders,
|
||||
)
|
||||
.then((res) => {
|
||||
.then((res) =>
|
||||
{
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return { stargazers_count: 0 };
|
||||
});
|
||||
|
|
@ -23,21 +25,25 @@ export const releaseData = await fetch(
|
|||
"https://api.github.com/repos/simeonradivoev/gameflow-deck/releases/latest",
|
||||
githubHeaders,
|
||||
)
|
||||
.then((res) => {
|
||||
.then((res) =>
|
||||
{
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return { tag_name: "unknown" };
|
||||
});
|
||||
|
||||
async function getTotalDownloads(owner: string, repo: string): Promise<number> {
|
||||
async function getTotalDownloads (owner: string, repo: string): Promise<number>
|
||||
{
|
||||
let totalDownloads = 0;
|
||||
let cursor: string | null = null;
|
||||
let hasNextPage = true;
|
||||
|
||||
while (hasNextPage) {
|
||||
while (hasNextPage)
|
||||
{
|
||||
const res = await fetch("https://api.github.com/graphql", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -65,13 +71,15 @@ async function getTotalDownloads(owner: string, repo: string): Promise<number> {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (!res.ok)
|
||||
{
|
||||
throw new Error(`GitHub API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const json: any = await res.json();
|
||||
|
||||
if (json.errors) {
|
||||
if (json.errors)
|
||||
{
|
||||
throw new Error(
|
||||
`GraphQL error: ${json.errors.map((e: any) => e.message).join(", ")}`,
|
||||
);
|
||||
|
|
@ -79,14 +87,17 @@ async function getTotalDownloads(owner: string, repo: string): Promise<number> {
|
|||
|
||||
const releases = json.data?.repository?.releases;
|
||||
|
||||
if (!releases) {
|
||||
if (!releases)
|
||||
{
|
||||
throw new Error(
|
||||
`Repository ${owner}/${repo} not found or not accessible`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const release of releases.nodes) {
|
||||
for (const asset of release.releaseAssets.nodes) {
|
||||
for (const release of releases.nodes)
|
||||
{
|
||||
for (const asset of release.releaseAssets.nodes)
|
||||
{
|
||||
totalDownloads += asset.downloadCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +112,8 @@ async function getTotalDownloads(owner: string, repo: string): Promise<number> {
|
|||
export const totalDownloads = await getTotalDownloads(
|
||||
"simeonradivoev",
|
||||
"gameflow-deck",
|
||||
).catch((e) => {
|
||||
).catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return 0;
|
||||
});
|
||||
|
|
@ -110,11 +122,13 @@ export const appContributorsData = await fetch(
|
|||
"https://api.github.com/repos/simeonradivoev/gameflow-deck/contributors",
|
||||
githubHeaders,
|
||||
)
|
||||
.then((res) => {
|
||||
.then((res) =>
|
||||
{
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return [];
|
||||
});
|
||||
|
|
@ -123,11 +137,13 @@ export const storeContributorsData = await fetch(
|
|||
"https://api.github.com/repos/simeonradivoev/gameflow-store/contributors",
|
||||
githubHeaders,
|
||||
)
|
||||
.then((res) => {
|
||||
.then((res) =>
|
||||
{
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return [];
|
||||
});
|
||||
|
|
@ -137,7 +153,16 @@ export const emulators = await fetch(
|
|||
)
|
||||
.then((res) => res.json())
|
||||
.then((d) => d.emulators as any[])
|
||||
.catch((e) => {
|
||||
.catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
return [] as any[];
|
||||
});
|
||||
|
||||
export const plugins = await fetch('https://registry.npmjs.com/-/v1/search?text=keywords:gameflow-plugin')
|
||||
.then(res => res.json())
|
||||
.catch(e =>
|
||||
{
|
||||
console.error(e);
|
||||
return [] as any[];
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue