Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions src/lib/Toasts.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts" module>
export type Toast = {
message: string;
style?: "error";
};

export function showToast(toast: Toast) {
toastQueue.push(toast);
mainLoop();
}

let toastQueue: Toast[] = [];
let currentToast: Toast | undefined = $state();

let active = false;
async function mainLoop() {
if (active) {
return;
}

active = true;

while (toastQueue.length > 0) {
currentToast = toastQueue.shift();
await wait(5_000);

currentToast = undefined;
await wait(1_000);
}

active = false;
}

function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
</script>

<script lang="ts">
import "$lib/hexagons.css";
import { fade, fly } from "svelte/transition";

let height: number = $state(48);
</script>

{#if currentToast != undefined}
<div
class="toast clip-hexagon {currentToast.style}"
in:fly={{ y: "100%" }}
out:fade
bind:clientHeight={height}
style:--height="{height}px"
>
{currentToast.message}
</div>
{/if}

<style>
.toast {
color: var(--background-color);
background-color: var(--foreground-color);
font-size: 14pt;
font-weight: 400;
position: fixed;
left: 0;
bottom: 0;
margin: 24px;
min-height: 48px;
padding: 12px calc(var(--height) / 3.4641 + 6px);
display: grid;
place-content: center;
}

.toast.error {
background-color: var(--red);
color: white;
}

@media (width < 600px) {
.toast {
right: 0;
}
}
</style>
12 changes: 12 additions & 0 deletions src/lib/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, expect, it } from "vitest";
import { getErrorMessage } from "./errors";

describe("getErrorMessage", () => {
it("should return string unchanged", () => {
expect(getErrorMessage("example string")).toBe("example string");
});

it("should get message from error", () => {
expect(getErrorMessage(new Error("example message"))).toBe("example message");
});
});
42 changes: 42 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { showToast } from "./Toasts.svelte";

export function getErrorMessage(error: unknown): string {
if (typeof error == "string") {
return error;
}

if (
typeof error == "object" &&
error != null &&
"message" in error &&
typeof error.message == "string"
) {
return error.message;
}

console.error("unknown error", error);
return String(error);
}

export function toastError(error: unknown) {
console.error(error);

const message = getErrorMessage(error);

// eslint-disable-next-line @typescript-eslint/no-unsafe-call -- this is just eslint getting confused by svelte
showToast({ message, style: "error" });
}

export async function assertOk(response: Response | Promise<Response>): Promise<Response> {
response = await response;

if (response.ok) {
return response;
}

if (response.headers.get("Content-Type")?.startsWith("application/json")) {
throw await response.json();
} else {
throw await response.text();
}
}
6 changes: 4 additions & 2 deletions src/lib/themes.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:root {
color-scheme: light dark;
background-color: light-dark(white, #121212);
color: light-dark(black, white);
background-color: var(--background-color);
color: var(--foreground-color);
accent-color: var(--primary);

--red: oklch(57% 0.26 28deg);
Expand All @@ -12,4 +12,6 @@
--purple: oklch(48% 0.24 311deg);
--pink: oklch(77% 0.19 356deg);
--primary: light-dark(var(--blue), var(--aqua));
--background-color: light-dark(white, #121212);
--foreground-color: light-dark(black, white);
}
9 changes: 9 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import Modal from "$lib/Modal.svelte";
import EditNameForm from "$lib/EditNameForm.svelte";
import AccountModal from "$lib/AccountModal.svelte";
import Toasts from "$lib/Toasts.svelte";
import { toastError } from "$lib/errors.js";

let { data, children } = $props();
let { supabase, session, user, profilePromise, lightModeCookie } = $derived(data);
Expand Down Expand Up @@ -54,6 +56,11 @@
<link rel="preconnect" href={PUBLIC_SUPABASE_URL} />
</svelte:head>

<svelte:window
onerror={(event) => toastError(event instanceof ErrorEvent ? event.error : event)}
onunhandledrejection={(event) => toastError(event.reason)}
/>

<header>
<h1><a href="/">Connexagon</a></h1>

Expand All @@ -78,6 +85,8 @@
<AccountModal {supabase} {user} {profilePromise} bind:open={showAccountModal} />
{/if}

<Toasts />

<style>
header {
padding: 12px;
Expand Down
10 changes: 8 additions & 2 deletions src/routes/games/[game_id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Board from "$lib/Board.svelte";
import Button from "$lib/Button.svelte";
import type { Tables } from "$lib/database-types";
import { assertOk } from "$lib/errors.js";

const { data } = $props();
const { supabase, user } = $derived(data);
Expand Down Expand Up @@ -99,11 +100,16 @@
let selection: number[] = $state([]);

async function startGame() {
await fetch(`/games/${game.id}/start`, { method: "POST" });
await assertOk(fetch(`/games/${game.id}/start`, { method: "POST" }));
}

async function makeTurn() {
await fetch(`/games/${game.id}/turns`, { method: "POST", body: JSON.stringify(selection) });
await assertOk(
fetch(`/games/${game.id}/turns`, {
method: "POST",
body: JSON.stringify(selection),
}),
);
selection = [];
}

Expand Down
Loading