mirror of
https://github.com/Xevion/Pac-Man.git
synced 2026-01-31 04:25:07 -06:00
refactor(web): extract NavLink component and add Prettier config
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ComponentType } from 'svelte';
|
||||||
|
|
||||||
|
let {
|
||||||
|
href,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
active = false,
|
||||||
|
size = 18
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
icon: ComponentType;
|
||||||
|
label: string;
|
||||||
|
active?: boolean;
|
||||||
|
size?: number;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
function navLinkClass(active: boolean): string {
|
||||||
|
return `flex items-center gap-1.5 tracking-wide transition-colors duration-200 ${
|
||||||
|
active ? 'text-white' : 'text-gray-500 hover:text-gray-300'
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {href} class={navLinkClass(active)}>
|
||||||
|
<icon {size}></icon>
|
||||||
|
<span>{label}</span>
|
||||||
|
</a>
|
||||||
@@ -6,7 +6,7 @@ export interface PacmanModule {
|
|||||||
_stop_game?: () => void;
|
_stop_game?: () => void;
|
||||||
_restart_game?: () => void;
|
_restart_game?: () => void;
|
||||||
locateFile: (path: string) => string;
|
locateFile: (path: string) => string;
|
||||||
preRun: unknown[];
|
preRun: Array<() => void>;
|
||||||
// Emscripten lifecycle hooks
|
// Emscripten lifecycle hooks
|
||||||
onAbort?: (what: unknown) => void;
|
onAbort?: (what: unknown) => void;
|
||||||
onRuntimeInitialized?: () => void;
|
onRuntimeInitialized?: () => void;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { onNavigate } from '$app/navigation';
|
import { onNavigate } from '$app/navigation';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-svelte';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-svelte';
|
||||||
@@ -10,11 +11,11 @@
|
|||||||
IconDeviceGamepad3,
|
IconDeviceGamepad3,
|
||||||
IconTrophy
|
IconTrophy
|
||||||
} from '@tabler/icons-svelte';
|
} from '@tabler/icons-svelte';
|
||||||
|
import NavLink from '$lib/components/NavLink.svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
let opened = $state(false);
|
let opened = $state(false);
|
||||||
let mounted = $state(false);
|
|
||||||
|
|
||||||
// Keys that the game uses - only these should reach SDL/Emscripten on the play page
|
// Keys that the game uses - only these should reach SDL/Emscripten on the play page
|
||||||
const GAME_KEYS = new Set([
|
const GAME_KEYS = new Set([
|
||||||
@@ -41,8 +42,6 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true;
|
|
||||||
|
|
||||||
// Global keyboard filter to prevent SDL/Emscripten from capturing keys.
|
// Global keyboard filter to prevent SDL/Emscripten from capturing keys.
|
||||||
// SDL's handlers persist globally even after navigating away from the play page.
|
// SDL's handlers persist globally even after navigating away from the play page.
|
||||||
// This filter ensures browser shortcuts (F5, F12, Ctrl+R, etc.) always work.
|
// This filter ensures browser shortcuts (F5, F12, Ctrl+R, etc.) always work.
|
||||||
@@ -152,7 +151,7 @@
|
|||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<title>Pac-Man</title>
|
<title>Pac-Man</title>
|
||||||
<meta name="description" content="A Pac-Man game built with Rust and React." />
|
<meta name="description" content="A Pac-Man game built with Rust and Svelte." />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="bg-black text-yellow-400 h-screen flex flex-col overflow-hidden">
|
<div class="bg-black text-yellow-400 h-screen flex flex-col overflow-hidden">
|
||||||
@@ -168,15 +167,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex items-center gap-8">
|
<div class="flex items-center gap-8">
|
||||||
<a
|
<NavLink href="/leaderboard" icon={IconTrophy} label="Leaderboard" active={isActive('/leaderboard')} />
|
||||||
href="/leaderboard"
|
|
||||||
class="flex items-center gap-1.5 tracking-wide transition-colors duration-200 {isActive('/leaderboard')
|
|
||||||
? 'text-white'
|
|
||||||
: 'text-gray-500 hover:text-gray-300'}"
|
|
||||||
>
|
|
||||||
<IconTrophy size={18} />
|
|
||||||
<span>Leaderboard</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
@@ -197,15 +188,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<NavLink href="/download" icon={IconDownload} label="Download" active={isActive('/download')} />
|
||||||
href="/download"
|
|
||||||
class="flex items-center gap-1.5 tracking-wide transition-colors duration-200 {isActive('/download')
|
|
||||||
? 'text-white'
|
|
||||||
: 'text-gray-500 hover:text-gray-300'}"
|
|
||||||
>
|
|
||||||
<IconDownload size={18} />
|
|
||||||
<span>Download</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute right-4 hidden sm:flex gap-4 items-center">
|
<div class="absolute right-4 hidden sm:flex gap-4 items-center">
|
||||||
@@ -223,7 +206,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if mounted}
|
{#if browser}
|
||||||
<OverlayScrollbarsComponent
|
<OverlayScrollbarsComponent
|
||||||
defer
|
defer
|
||||||
options={{
|
options={{
|
||||||
@@ -247,8 +230,18 @@
|
|||||||
|
|
||||||
{#if opened}
|
{#if opened}
|
||||||
<div class="fixed inset-0 z-30">
|
<div class="fixed inset-0 z-30">
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<div
|
||||||
<div role="button" tabindex="-1" class="absolute inset-0 bg-black/60" onclick={close}></div>
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
class="absolute inset-0 bg-black/60"
|
||||||
|
onclick={close}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
<div
|
<div
|
||||||
class="absolute left-0 top-0 h-full w-72 max-w-[80vw] bg-black border-r border-yellow-400/25 p-4"
|
class="absolute left-0 top-0 h-full w-72 max-w-[80vw] bg-black border-r border-yellow-400/25 p-4"
|
||||||
>
|
>
|
||||||
@@ -264,15 +257,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
{#each links as link}
|
{#each links as link}
|
||||||
<a
|
<NavLink href={link.href} icon={link.icon} label={link.label} active={isActive(link.href)} size={28} />
|
||||||
href={link.href}
|
|
||||||
class="flex items-center gap-1.5 tracking-wide transition-colors duration-200 {isActive(link.href)
|
|
||||||
? 'text-white'
|
|
||||||
: 'text-gray-500 hover:text-gray-300'}"
|
|
||||||
>
|
|
||||||
<link.icon size={28} />
|
|
||||||
<span>{link.label}</span>
|
|
||||||
</a>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+16
-16
@@ -12,23 +12,22 @@
|
|||||||
let loadError = $state<LoadingError | null>(null);
|
let loadError = $state<LoadingError | null>(null);
|
||||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
// Fade out loading overlay when game becomes ready
|
// Manage loading overlay fade and timeout cleanup
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
if (gameReady || loadError) {
|
||||||
|
// Clear loading timeout when ready or error
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out loading overlay when ready
|
||||||
if (gameReady && loadingVisible) {
|
if (gameReady && loadingVisible) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
loadingVisible = false;
|
loadingVisible = false;
|
||||||
}, LOADING_FADE_DURATION);
|
}, LOADING_FADE_DURATION);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Clear timeout when game is ready or error occurs
|
|
||||||
$effect(() => {
|
|
||||||
if (gameReady || loadError) {
|
|
||||||
if (timeoutId) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
timeoutId = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,11 +63,7 @@
|
|||||||
|
|
||||||
// Restart game when returning to this page
|
// Restart game when returning to this page
|
||||||
afterNavigate(() => {
|
afterNavigate(() => {
|
||||||
requestAnimationFrame(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
restartGame();
|
restartGame();
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function restartGame() {
|
function restartGame() {
|
||||||
@@ -209,14 +204,19 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<div class="flex justify-center items-center h-full pt-4">
|
<div class="flex justify-center items-center h-full pt-4">
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="-1"
|
tabindex="0"
|
||||||
class="relative block aspect-[5/6]"
|
class="relative block aspect-[5/6]"
|
||||||
style="height: min(calc(100vh - 96px), calc((100vw - 32px) * 6 / 5));"
|
style="height: min(calc(100vh - 96px), calc((100vw - 32px) * 6 / 5));"
|
||||||
onclick={handleInteraction}
|
onclick={handleInteraction}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleInteraction();
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<canvas id="canvas" tabindex="-1" class="w-full h-full" onclick={focusCanvas}></canvas>
|
<canvas id="canvas" tabindex="-1" class="w-full h-full" onclick={focusCanvas}></canvas>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
<script lang="ts">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
Reference in New Issue
Block a user