Files
Ely-GPUI-Components/frontend/src/apps.ts
T
ZacharyZhang-NY faf0987cf8 Site · Showcase: apps built with Ely, from the public Ely-GPUI-Showcases repo at a pinned commit
The build fetches showcase.lock's commit and runs the catalog's own validator. /showcase/ lists featured apps first, then the rest by newest or stars, with categories and search; /showcase/<id>/ shows previews, links and the README. The header drops its sound switch below 414px so the fourth link fits.
2026-10-04 21:39:13 -04:00

62 lines
2.2 KiB
TypeScript

/** An app from the showcase catalog, as `scripts/showcase.mjs` writes it. */
export interface App {
id: string;
name: string;
author: string;
description: string;
category: string;
platforms: string[];
website: string | null;
source: string | null;
publishedAt: string;
previews: string[];
building?: boolean;
stars?: number;
starsUpdatedAt?: string;
/** The README, rendered; null without one. */
html: string | null;
}
export interface Catalog {
source: string;
categories: Record<string, string>;
featured: string[];
apps: App[];
}
export type Sort = "newest" | "stars";
export const SUBMIT = "https://github.com/ZacharyZhang-NY/Ely-GPUI-Showcases#submit-an-app";
/** Newest first, then most stars; or most stars, then newest. */
export function sorted(apps: App[], sort: Sort): App[] {
const time = (a: App) => Date.parse(a.publishedAt);
const stars = (a: App) => a.stars ?? -1;
const [first, second] = sort === "newest" ? [time, stars] : [stars, time];
return [...apps].sort((a, b) => first(b) - first(a) || second(b) - second(a) || a.name.localeCompare(b.name));
}
/** The featured apps in their order, and the rest. */
export function split(catalog: Catalog): { featured: App[]; rest: App[] } {
const byId = new Map(catalog.apps.map((a) => [a.id, a]));
const featured = catalog.featured.map((id) => {
const app = byId.get(id);
if (!app) throw new Error(`showcase: featured ${id} is no app`);
return app;
});
return { featured, rest: catalog.apps.filter((a) => !catalog.featured.includes(a.id)) };
}
/** Apps in a category, or all with none, whose name, author or description holds the query. */
export function filtered(apps: App[], query: string, category: string | null): App[] {
const q = query.trim().toLowerCase();
return apps.filter((a) => (!category || a.category === category) && (!q || [a.name, a.author, a.description].some((s) => s.toLowerCase().includes(q))));
}
/** How many apps each category holds. */
export function counts(catalog: Catalog): [string, string, number][] {
return Object.entries(catalog.categories).map(([key, label]) => [key, label, catalog.apps.filter((a) => a.category === key).length]);
}
export const cover = (app: App) => `/showcase/${app.id}/${app.previews[0]}`;