1
0
Fork 0

Redesign index

This commit is contained in:
Pabloader 2026-04-10 14:30:43 +00:00
parent 7b9019bd9d
commit 87f582590b
8 changed files with 120 additions and 32 deletions

View File

@ -13,17 +13,16 @@ const publish = process.env.PUBLISH_LOCATION;
const args = process.argv.slice(2);
const local = args.includes('--local');
const exported = args.includes('--exported');
let game = args.find(a => !a.startsWith('-')) ?? '';
while (!await isGame(game)) {
game = await select({
message: 'Game to build:',
choices: (await getGames()).map(value => ({ value })),
choices: (await getGames()).map(game => ({ value: game.name })),
});
}
console.log(`Building ${game}...`);
const html = await buildHTML(game, { production: true, local, exported });
const html = await buildHTML(game, { production: true, local });
if (!html) {
process.exit(1);

View File

@ -13,7 +13,7 @@ import filePlugin from './plugins/filePlugin';
import { getGames } from './isGame';
const b64 = async (file: string | BunFile) => (
export const b64 = async (file: string | BunFile) => (
typeof file === 'string'
? Buffer.from(file)
: Buffer.from(await file.arrayBuffer())
@ -33,7 +33,6 @@ interface Args {
production?: boolean;
mobile?: boolean;
local?: boolean;
exported?: boolean;
}
const SW_SCRIPT = `<script>
@ -49,8 +48,7 @@ if ('serviceWorker' in navigator) {
const CSS_RELOAD_SCRIPT = `<script>
function connect() {
const params = new URLSearchParams(location.search);
const game = params.get('game') || '';
if (!game) return;
const game = params.get('game') || 'index';
const ws = new WebSocket('css-ws?game=' + encodeURIComponent(game));
const onMessage = (e) => {
try {
@ -61,7 +59,7 @@ function connect() {
}
} catch { }
};
const onClose = () => {
const onClose = (e) => {
ws.removeEventListener('message', onMessage);
ws.removeEventListener('close', onClose);
setTimeout(connect, 500);

View File

@ -1,5 +1,6 @@
import path from 'path';
import fs from 'fs/promises';
import { b64 } from './html';
export async function isGame(name: string | null | undefined) {
if (!name || name === 'index') return false;
@ -13,7 +14,12 @@ export async function isGame(name: string | null | undefined) {
return stat.isDirectory();
}
export async function getGames() {
export interface Game {
name: string;
iconUrl?: string;
}
export async function getGames(): Promise<Game[]> {
const dir = path.resolve(import.meta.dir, '..', 'src', 'games');
if (!await fs.exists(dir)) return [];
@ -21,5 +27,20 @@ export async function getGames() {
if (!stat.isDirectory()) return [];
const list = await fs.readdir(dir);
return list.filter(d => d !== 'index').sort();
const gameNames = list.filter(d => d !== 'index').sort();
const games: Game[] = [];
for (const name of gameNames) {
const gameDir = path.resolve(dir, name);
const pwaIconFile = Bun.file(path.resolve(gameDir, 'assets', 'pwa_icon.png'));
const iconFile = Bun.file(path.resolve(gameDir, 'assets', 'favicon.ico'));
let iconUrl: string | undefined;
if (await pwaIconFile.exists()) {
iconUrl = `data:image/png;base64,${await b64(pwaIconFile)}`;
} else if (await iconFile.exists()) {
iconUrl = `data:image/x-icon;base64,${await b64(iconFile)}`;
}
games.push({ name, iconUrl });
}
return games;
}

View File

@ -70,11 +70,8 @@ Bun.serve<ClientData>({
switch (pathname) {
case 'css-ws': {
if (!(await isGame(gameParam))) {
return new Response('Unknown game', { status: 400 });
}
const upgraded = server.upgrade(req, {
data: { game: gameParam! } satisfies ClientData,
data: { game } satisfies ClientData,
});
if (upgraded) return undefined;
return new Response('Upgrade failed', { status: 500 });

View File

@ -0,0 +1 @@
isApp: true

View File

@ -0,0 +1,68 @@
.games {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-height: 100vh;
padding: 40px 20%;
background: var(--bg);
color: var(--text);
font-family: inherit;
}
.title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 32px;
color: var(--text);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
width: 100%;
}
.card {
composes: card from '@common/assets/ui.module.css';
flex-direction: column;
align-items: center;
gap: 12px;
text-decoration: none;
color: var(--text);
cursor: pointer;
border: 1px solid var(--border);
transition: all var(--transition);
}
.card:hover {
transform: translateY(-2px);
border-color: var(--accent);
}
.icon {
width: 64px;
height: 64px;
border-radius: var(--radius);
object-fit: cover;
}
.placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
border-radius: var(--radius);
background: var(--bg-active);
font-size: 1.5rem;
font-weight: 700;
color: var(--text-muted);
}
.cardName {
font-size: 1rem;
font-weight: 500;
text-align: center;
}

View File

@ -1,11 +0,0 @@
body {
display: flex;
}
.games {
display: flex;
flex-direction: column;
width: 100%;
padding: 40px 20%;
gap: 20px;
}

View File

@ -1,12 +1,27 @@
import { render } from "preact";
import styles from './game.module.css';
import styles from "./assets/game.module.css";
import type { Game } from "../../../build/isGame";
declare const GAMES: string[];
declare const GAMES: Game[];
function GameList({ games }: { games: Game[] }) {
return (
<div class={styles.games}>
<h1 class={styles.title}>Games</h1>
<div class={styles.grid}>
{games.map((game) => (
<a key={game.name} href={`?game=${game.name}`} class={styles.card}>
{game.iconUrl
? <img src={game.iconUrl} alt={game.name} class={styles.icon} />
: <span class={styles.placeholder}>{game.name[0].toUpperCase()}</span>}
<span class={styles.cardName}>{game.name}</span>
</a>
))}
</div>
</div>
);
}
export default function run() {
const root = <div class={styles.games}>
{GAMES.map(g => <a key={g} href={`?game=${g}`}>{g}</a>)}
</div>;
render(root, document.body);
render(<GameList games={GAMES} />, document.body);
}