Redesign index
This commit is contained in:
parent
7b9019bd9d
commit
87f582590b
|
|
@ -13,17 +13,16 @@ const publish = process.env.PUBLISH_LOCATION;
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const local = args.includes('--local');
|
const local = args.includes('--local');
|
||||||
const exported = args.includes('--exported');
|
|
||||||
let game = args.find(a => !a.startsWith('-')) ?? '';
|
let game = args.find(a => !a.startsWith('-')) ?? '';
|
||||||
|
|
||||||
while (!await isGame(game)) {
|
while (!await isGame(game)) {
|
||||||
game = await select({
|
game = await select({
|
||||||
message: 'Game to build:',
|
message: 'Game to build:',
|
||||||
choices: (await getGames()).map(value => ({ value })),
|
choices: (await getGames()).map(game => ({ value: game.name })),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(`Building ${game}...`);
|
console.log(`Building ${game}...`);
|
||||||
const html = await buildHTML(game, { production: true, local, exported });
|
const html = await buildHTML(game, { production: true, local });
|
||||||
|
|
||||||
if (!html) {
|
if (!html) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import filePlugin from './plugins/filePlugin';
|
||||||
|
|
||||||
import { getGames } from './isGame';
|
import { getGames } from './isGame';
|
||||||
|
|
||||||
const b64 = async (file: string | BunFile) => (
|
export const b64 = async (file: string | BunFile) => (
|
||||||
typeof file === 'string'
|
typeof file === 'string'
|
||||||
? Buffer.from(file)
|
? Buffer.from(file)
|
||||||
: Buffer.from(await file.arrayBuffer())
|
: Buffer.from(await file.arrayBuffer())
|
||||||
|
|
@ -33,7 +33,6 @@ interface Args {
|
||||||
production?: boolean;
|
production?: boolean;
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
exported?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SW_SCRIPT = `<script>
|
const SW_SCRIPT = `<script>
|
||||||
|
|
@ -49,8 +48,7 @@ if ('serviceWorker' in navigator) {
|
||||||
const CSS_RELOAD_SCRIPT = `<script>
|
const CSS_RELOAD_SCRIPT = `<script>
|
||||||
function connect() {
|
function connect() {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const game = params.get('game') || '';
|
const game = params.get('game') || 'index';
|
||||||
if (!game) return;
|
|
||||||
const ws = new WebSocket('css-ws?game=' + encodeURIComponent(game));
|
const ws = new WebSocket('css-ws?game=' + encodeURIComponent(game));
|
||||||
const onMessage = (e) => {
|
const onMessage = (e) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -61,7 +59,7 @@ function connect() {
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
};
|
};
|
||||||
const onClose = () => {
|
const onClose = (e) => {
|
||||||
ws.removeEventListener('message', onMessage);
|
ws.removeEventListener('message', onMessage);
|
||||||
ws.removeEventListener('close', onClose);
|
ws.removeEventListener('close', onClose);
|
||||||
setTimeout(connect, 500);
|
setTimeout(connect, 500);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import { b64 } from './html';
|
||||||
|
|
||||||
export async function isGame(name: string | null | undefined) {
|
export async function isGame(name: string | null | undefined) {
|
||||||
if (!name || name === 'index') return false;
|
if (!name || name === 'index') return false;
|
||||||
|
|
@ -13,7 +14,12 @@ export async function isGame(name: string | null | undefined) {
|
||||||
return stat.isDirectory();
|
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');
|
const dir = path.resolve(import.meta.dir, '..', 'src', 'games');
|
||||||
if (!await fs.exists(dir)) return [];
|
if (!await fs.exists(dir)) return [];
|
||||||
|
|
||||||
|
|
@ -21,5 +27,20 @@ export async function getGames() {
|
||||||
if (!stat.isDirectory()) return [];
|
if (!stat.isDirectory()) return [];
|
||||||
|
|
||||||
const list = await fs.readdir(dir);
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -70,11 +70,8 @@ Bun.serve<ClientData>({
|
||||||
|
|
||||||
switch (pathname) {
|
switch (pathname) {
|
||||||
case 'css-ws': {
|
case 'css-ws': {
|
||||||
if (!(await isGame(gameParam))) {
|
|
||||||
return new Response('Unknown game', { status: 400 });
|
|
||||||
}
|
|
||||||
const upgraded = server.upgrade(req, {
|
const upgraded = server.upgrade(req, {
|
||||||
data: { game: gameParam! } satisfies ClientData,
|
data: { game } satisfies ClientData,
|
||||||
});
|
});
|
||||||
if (upgraded) return undefined;
|
if (upgraded) return undefined;
|
||||||
return new Response('Upgrade failed', { status: 500 });
|
return new Response('Upgrade failed', { status: 500 });
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
isApp: true
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.games {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
padding: 40px 20%;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
import { render } from "preact";
|
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() {
|
export default function run() {
|
||||||
const root = <div class={styles.games}>
|
render(<GameList games={GAMES} />, document.body);
|
||||||
{GAMES.map(g => <a key={g} href={`?game=${g}`}>{g}</a>)}
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
render(root, document.body);
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue