Compare commits

..

No commits in common. "6a49c361a05d7bf72c7847237a8e394e5677eb7b" and "c939fb80d3881b4fbdd476cce36d12442a596e96" have entirely different histories.

20 changed files with 74 additions and 186 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "yukiotoko", "name": "chuni-national-matchmaking-webui",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",

View File

@ -7,7 +7,7 @@
<title>yukiotoko</title> <title>yukiotoko</title>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover" class="bg-dark text-text"> <body data-sveltekit-preload-data="hover" class="bg-dark text-light">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -4,7 +4,7 @@
let { rank, ...rest }: { rank: number } & HTMLAttributes<HTMLDivElement> = $props(); let { rank, ...rest }: { rank: number } & HTMLAttributes<HTMLDivElement> = $props();
const [xOffset, yOffset] = $derived(imageOffset(rank)); const [xOffset, yOffset] = imageOffset(rank);
</script> </script>
<div {...rest}> <div {...rest}>

View File

@ -1,31 +1,16 @@
<script lang="ts"> <script lang="ts">
let { date }: { date: Date } = $props(); const { date }: { date: Date } = $props();
const difference = new Date().getTime() / 1000 - date.getTime() / 1000;
let currentDate = $state(Date.now()); const seconds = ~~(difference % 60);
let difference = $derived(currentDate / 1000 - date.getTime() / 1000); const minutes = ~~(difference / 60) % 60;
let seconds = $derived(~~(difference % 60)); const hours = ~~(difference / 3600) % 24;
let minutes = $derived(~~(difference / 60) % 60); const days = ~~(difference / 86400);
let hours = $derived(~~(difference / 3600) % 24);
let days = $derived(~~(difference / 86400));
setInterval(() => {
currentDate = Date.now();
});
</script> </script>
<span class="group text-light underline"> <p class="group underline">
<span class="group-hover:hidden"> <span class="group-hover:hidden">
about about
{#if days > 0} {#if minutes == 0}
<span>{days}d</span>
{:else if hours > 0}
<span>{hours}h</span>
{:else if minutes > 0}
<span>{minutes}m</span>
{:else}
<span>{seconds}s</span>
{/if}
<!-- {#if minutes == 0}
<span>{seconds}s</span> <span>{seconds}s</span>
{:else if hours == 0} {:else if hours == 0}
<span>{minutes}m</span> <span>{minutes}m</span>
@ -33,10 +18,10 @@
<span>{hours}h</span> <span>{hours}h</span>
{:else} {:else}
<span>{days}d</span> <span>{days}d</span>
{/if} --> {/if}
ago ago
</span> </span>
<span class="hidden group-hover:block"> <span class="hidden group-hover:block">
{date.toLocaleString()} {date.toLocaleString()}
</span> </span>
</span> </p>

View File

@ -1,4 +1,4 @@
<footer class="bg-medium text-light"> <footer class="bg-medium">
<div class="mx-auto flex w-full max-w-screen-xl flex-col p-4 md:flex-row md:justify-between"> <div class="mx-auto flex w-full max-w-screen-xl flex-col p-4 md:flex-row md:justify-between">
<p> <p>
made with <a href="https://svelte.dev/">sveltekit</a>, powered by made with <a href="https://svelte.dev/">sveltekit</a>, powered by
@ -6,8 +6,8 @@
</p> </p>
<p> <p>
wana help out, or issue a bug? you can find the repo <a you can find the repo of the website <a href="https://tea.metatable.sh/meta/yukiotoko-webui"
href="https://tea.metatable.sh/meta/yukiotoko-webui">here</a >here</a
> >
</p> </p>
</div> </div>

View File

@ -5,7 +5,7 @@
let { version, ...rest }: { version: string } & HTMLAttributes<HTMLImageElement> = $props(); let { version, ...rest }: { version: string } & HTMLAttributes<HTMLImageElement> = $props();
const { minor } = $derived(fromVersionString(version)); const { minor } = fromVersionString(version);
</script> </script>
<img src="/versions/{gameVersions[minor.toString()]}" alt={version} {...rest} /> <img src="/versions/{gameVersions[minor.toString()]}" alt={version} {...rest} />

View File

@ -1,40 +0,0 @@
<script lang="ts" generics="T">
let {
items,
itemsPerPage,
pageItems = $bindable()
}: { items: Array<T>; itemsPerPage: number; pageItems: Array<T> } = $props();
let currentPage = $state(0);
let pageIndexStart = $derived(currentPage * itemsPerPage);
let pageIndexEnd = $derived(Math.min(itemsPerPage * (currentPage + 1), items.length));
$effect(() => {
currentPage = Math.min(Math.max(currentPage, 0), ~~(items.length / itemsPerPage));
pageItems = items.slice(pageIndexStart, pageIndexEnd);
});
</script>
<div class="flex flex-col gap-2">
<p>
showing <span class="font-bold text-light">{pageIndexStart + 1}</span> to
<span class="font-bold text-light">{pageIndexEnd}</span>
of <span class="font-bold text-light">{items.length}</span>
entires
</p>
<div class="flex divide-x divide-border">
<button class="rounded-bl-lg rounded-tl-lg text-light" onclick={() => currentPage--}
>prev</button
>
<button class="rounded-br-lg rounded-tr-lg text-light" onclick={() => currentPage++}
>next</button
>
</div>
</div>
<style>
button {
@apply bg-medium px-4 py-2 transition hover:brightness-150;
}
</style>

View File

@ -9,11 +9,13 @@
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<p class="font-bold text-light">{player.name}</p> <p>{player.name}</p>
<div class="flex gap-2"> <div class="flex gap-2">
<Rating rating={player.rating} /> <Rating rating={player.rating} />
<BattleRank rank={player.battleRank} class="w-16" /> <BattleRank rank={player.battleRank} class="w-16" />
</div> </div>
</div> </div>
<Team team={player?.team ?? { name: 'No Team', rank: 0 }} /> {#if player.team}
<Team team={player.team} />
{/if}
</div> </div>

View File

@ -4,8 +4,8 @@
let { rating }: { rating: number } = $props(); let { rating }: { rating: number } = $props();
const characters = $derived(rating.toString().padStart(4, '0')); const characters = rating.toString().padStart(4, '0');
const type = $derived(ratingType(rating)); const type = ratingType(rating);
</script> </script>
<div class="flex items-center"> <div class="flex items-center">

View File

@ -8,7 +8,7 @@
...rest ...rest
}: { character: number | '.'; type: number } & HTMLAttributes<HTMLDivElement> = $props(); }: { character: number | '.'; type: number } & HTMLAttributes<HTMLDivElement> = $props();
const [xOffset, yOffset] = $derived(imageOffset(character, type)); const [xOffset, yOffset] = imageOffset(character, type);
</script> </script>
<div {...rest}> <div {...rest}>

View File

@ -1,44 +1,20 @@
<script lang="ts"> <script lang="ts">
import { User, Calendar } from 'lucide-svelte'; import { User, Calendar } from 'lucide-svelte';
import { onMount } from 'svelte';
import Player from './Player.svelte'; import Player from './Player.svelte';
import BattleRank from './BattleRank.svelte'; import BattleRank from './BattleRank.svelte';
import GameVersion from './GameVersion.svelte'; import GameVersion from './GameVersion.svelte';
import Separator from './Separator.svelte'; import Separator from './Separator.svelte';
import Date from './Date.svelte';
import axios from 'axios';
import type { Room } from './types'; import type { Room } from './types';
import Date from './Date.svelte';
let { room }: { room: Room } = $props(); let { room }: { room: Room } = $props();
let roomTimer = $state(room.secondsRemaining); let secondsRemaining = $state(room.secondsRemaining);
let players = $state(room.players);
onMount(() => { const interval = setInterval(() => {
if (!room.finished) { if (secondsRemaining <= 0 || room.finished) clearInterval(interval);
const roomTimerUpdateInterval = setInterval(() => { secondsRemaining--;
if (roomTimer <= 0) {
roomTimer = 0;
return;
}
roomTimer--;
}, 1000); }, 1000);
const roomTimerSyncInterval = setInterval(async () => {
console.log('updating');
const updatedRoom = await axios
.get<Room>(`/api/room/${room.roomId}`)
.then((result) => result.data);
roomTimer = updatedRoom.secondsRemaining;
players = updatedRoom.players;
if (updatedRoom.finished) {
console.log('killing invervals');
clearInterval(roomTimerUpdateInterval);
clearInterval(roomTimerSyncInterval);
}
}, 5000);
}
});
</script> </script>
<div class="flex flex-col gap-8 rounded-lg bg-medium p-6"> <div class="flex flex-col gap-8 rounded-lg bg-medium p-6">
@ -51,19 +27,19 @@
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<p class="flex items-center gap-2">game version <Separator /> {room.gameVersion}</p> <p class="flex items-center gap-2 text-sub">game version <Separator /> {room.gameVersion}</p>
<p class="flex items-center gap-2"> <p class="flex items-center gap-2 text-sub">
room battle rank <Separator /> room battle rank <Separator />
{room.roomBattleRank} {room.roomBattleRank}
</p> </p>
{#if !room.finished} {#if !room.finished}
<p class="flex items-center gap-2">seconds remaining <Separator /> {roomTimer}s</p> <p class="flex items-center gap-2">seconds remaining <Separator /> {secondsRemaining}s</p>
{/if} {/if}
</div> </div>
</div> </div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<p class="flex items-center gap-2 text-light"> <p class="flex items-center gap-2">
<User class="inline-block w-6" /> <User class="inline-block w-6" />
{room.players.length} / 4 {room.players.length} / 4
<Separator /> <Separator />
@ -72,7 +48,7 @@
</p> </p>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:grid-rows-2"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:grid-rows-2">
{#each players as player} {#each room.players as player}
<Player {player} /> <Player {player} />
{/each} {/each}
</div> </div>

View File

@ -1 +1,3 @@
<span class="inline-block h-[2px] w-4 rounded-full bg-current brightness-50"></span> <span class="inline-block"
><div class="h-[2px] w-4 rounded-full bg-current brightness-50"></div></span
>

View File

@ -11,7 +11,7 @@
? 'team-second' ? 'team-second'
: team.rank == 3 : team.rank == 3
? 'team-third' ? 'team-third'
: 'text-light'}" : ''}"
> >
<p> <p>
{team.name} {team.name}

View File

@ -1,5 +1,3 @@
export function arrayPadEnd<T>(array: T[], amount: number, data: T) { export function arrayPadEnd<T>(array: T[], amount: number, data: T) {
if (amount > array.length) { for (let i = 0; i < amount - array.length; i++) array.push(data);
array.push(...Array(amount - array.length).fill(data));
}
} }

View File

@ -20,6 +20,7 @@ function toPlayer(apiPlayer: APIPlayer): Player {
function toRoom(apiRoom: APIRoom): Room { function toRoom(apiRoom: APIRoom): Room {
return { return {
id: apiRoom.id,
roomId: apiRoom.roomId, roomId: apiRoom.roomId,
createdAt: new Date(apiRoom.updatedAt), createdAt: new Date(apiRoom.updatedAt),
gameVersion: apiRoom.dataVersion, gameVersion: apiRoom.dataVersion,
@ -30,12 +31,10 @@ function toRoom(apiRoom: APIRoom): Room {
}; };
} }
function validRoom(room: Room): boolean {
return !greaterThan(fromVersionString(room.gameVersion), fromVersionString(env.MAX_VERSION));
}
function filterRooms(rooms: Room[]): Room[] { function filterRooms(rooms: Room[]): Room[] {
return rooms.filter((room) => validRoom(room)); return rooms.filter(
(room) => !greaterThan(fromVersionString(room.gameVersion), fromVersionString(env.MAX_VERSION))
);
} }
export async function fetchRooms() { export async function fetchRooms() {
@ -57,20 +56,7 @@ export async function fetchRooms() {
}; };
} }
export async function fetchRoom(roomId: number | string): Promise<Room | undefined> { // TODO!
const result = await axios.get<APIRoom[]>(`http://yukiotoko.chara.lol:9000/api/get/${roomId}`, { // export async function fetchRoom(roomId: string): Promise<Room> {
headers: { // return new Promise()
Authorization: env.YUKIOTOKO_API_TOKEN // }
}
});
const apiRoom = result.data[0];
if (!apiRoom) return;
const room = toRoom(apiRoom);
if (!validRoom(room)) return;
return room;
}

View File

@ -56,6 +56,7 @@ export interface APIPlayer {
} }
export interface Room { export interface Room {
id: string;
roomId: number; roomId: number;
createdAt: Date; createdAt: Date;
gameVersion: string; gameVersion: string;

View File

@ -1,20 +1,6 @@
import { fetchRooms } from '$lib/server/yukiotoko';
import { arrayPadEnd } from '$lib/array';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { fetchRooms } from '$lib/server/yukiotoko';
export const load: PageServerLoad = async ({ fetch }) => { export const load: PageServerLoad = async ({ fetch }) => {
const { activeRooms, archivedRooms } = await fetchRooms(); return await fetchRooms();
archivedRooms.forEach((archivedRoom) =>
arrayPadEnd(archivedRoom.players, 4, {
name: 'CPU',
battleRank: 0,
rating: 0
})
);
return {
activeRooms,
archivedRooms
};
}; };

View File

@ -1,20 +1,25 @@
<script lang="ts"> <script lang="ts">
import Pagination from '$lib/Pagination.svelte'; import { arrayPadEnd } from '$lib/array';
import Room from '$lib/Room.svelte'; import Room from '$lib/Room.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
let { data }: { data: PageData } = $props(); let props: { data: PageData } = $props();
let { activeRooms, archivedRooms } = data;
let paginationArchivedRooms = $state([]); let data = $state(props.data);
data.archivedRooms.forEach((archivedRoom) =>
arrayPadEnd(archivedRoom.players, 4, {
name: 'CPU',
battleRank: 0,
rating: 0
})
);
</script> </script>
<div class="mb-8 space-y-8"> <div class="mb-16 space-y-8">
<section> <section>
<h1 class="text-4xl font-bold text-light">yukiotoko webui</h1> <h1 class="text-4xl font-bold">yukiotoko webui</h1>
<p> <p>a frontend redesign for <a href="http://yukiotoko.chara.lol/">yukiotoko</a></p>
a frontend redesign for <a href="http://yukiotoko.chara.lol/">yukiotoko</a> made by metamethods
</p>
<p class="text-sub">refresh the page to fetch newer yukiotoko data</p> <p class="text-sub">refresh the page to fetch newer yukiotoko data</p>
</section> </section>
</div> </div>
@ -22,14 +27,14 @@
<div class="flex flex-col gap-8"> <div class="flex flex-col gap-8">
<section class="flex flex-col gap-4"> <section class="flex flex-col gap-4">
<div> <div>
<h1 class="text-2xl font-bold text-light"> <h1 class="text-2xl font-bold">
active rooms <span class="text-sub">({activeRooms.length})</span> active rooms <span class="text-sub">({data.activeRooms.length})</span>
</h1> </h1>
<p class="text-sub">all of the currently matchmaking rooms</p> <p class="text-sub">all of the currently matchmaking rooms</p>
</div> </div>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2"> <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
{#each activeRooms as activeRoom} {#each data.activeRooms as activeRoom}
<Room room={activeRoom} /> <Room room={activeRoom} />
{/each} {/each}
</div> </div>
@ -37,16 +42,14 @@
<section class="flex flex-col gap-4"> <section class="flex flex-col gap-4">
<div> <div>
<h1 class="text-2xl font-bold text-light"> <h1 class="text-2xl font-bold">
archived rooms <span class="text-sub">({data.archivedRooms.length})</span> archived rooms <span class="text-sub">({data.archivedRooms.length})</span>
</h1> </h1>
<p class="text-sub">rooms that were created from the last 24 hours</p> <p class="text-sub">rooms that were created from the last 24 hours</p>
</div> </div>
<Pagination items={archivedRooms} itemsPerPage={20} bind:pageItems={paginationArchivedRooms} />
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2"> <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
{#each paginationArchivedRooms as archivedRoom} {#each data.archivedRooms as archivedRoom}
<Room room={archivedRoom} /> <Room room={archivedRoom} />
{/each} {/each}
</div> </div>

View File

@ -1,10 +0,0 @@
import { fetchRoom } from '$lib/server/yukiotoko';
import { error, json } from '@sveltejs/kit';
export async function GET({ params }) {
const room = await fetchRoom(params.roomId);
if (!room) return error(404);
return json(room);
}

View File

@ -7,12 +7,11 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
dark: '#111117', dark: '#060710',
medium: '#1F2128', medium: '#0A0B17',
light: '#CFD3FA', light: '#CFD3FA',
text: '#9B9FC0', sub: '#4D4D80',
accent: '#8D74FF', accent: '#7E61FF'
border: '#353741'
}, },
fontFamily: { fontFamily: {