import { NextPage } from "next"; import { useQuery } from "@apollo/react-hooks"; import gql from "graphql-tag"; import Link from "next/link"; import React, { useState, useMemo, useRef, useEffect } from "react"; import styles from "~/components/styles.module.scss"; import * as schema from "~/components/schema"; import { GRAPHQL_ENDPOINT, withApollo } from "~/components/hooks/with-apollo"; import { useDebounced } from "~/components/hooks/use-debounced"; import { version as frontendVersion } from "~/../package.json"; import useProgressIndicator from "~/components/hooks/use-nprogress"; import LoadingBlock from "~/components/loading-block"; import Head from "next/head"; const searchSuggestions = [ "Braid", "WarCraft", "Burnout", "Pokemon", "Portal", "Metroid", "Celeste", "Super Mario World", "Spyro", "Mario Kart", "Shovel Knight", "Resident Evil", "Zelda", "Final Fantasy", "Dark Souls", "Quake", ]; export const HomePage: NextPage<{}> = () => { const home = useQuery(GetHomeStats); const gameIndex = useQuery(GetGameIndex, { ssr: false, }); const [searchSuggestion, setSearchSuggestion] = useState( searchSuggestions[0], ); const [targetName, setTargetName] = useState(""); const debouncedTargetName = useDebounced(targetName, 250); const debouncedTargetNameOrSuggestion = debouncedTargetName || searchSuggestion; useEffect(() => { while (true) { const index = Math.floor(Math.random() * searchSuggestions.length); if (searchSuggestion !== searchSuggestions[index]) { setSearchSuggestion(searchSuggestions[index]); break; } } }, []); const [targetGames, orError] = useMemo(() => { if (!gameIndex?.data) { return [null, "loading..."]; } const slugify = (s: string) => { return s .toLowerCase() .normalize("NFD") .replace(/[^a-z0-9+]/g, ""); }; const name = slugify(debouncedTargetNameOrSuggestion); const matches = gameIndex.data.games .filter( (game: schema.GetGameIndex_games) => slugify(game.name).includes(name) || slugify(game.srcSlug).includes(name), ) .sort((a: schema.GetGameIndex_games, b: schema.GetGameIndex_games) => { const aExact = slugify(a.srcSlug) === name || slugify(a.name) === name; const bExact = slugify(b.srcSlug) === name || slugify(b.name) === name; const aPrefix = name.startsWith(slugify(a.srcSlug)); const bPrefix = name.startsWith(slugify(b.srcSlug)); if (aExact && !bExact) return -1; else if (bExact && !aExact) return +1; else if (aPrefix && !bPrefix) return -1; else if (bPrefix && !aPrefix) return +1; else if (a.srcSlug.length < b.srcSlug.length) return -1; else if (a.srcSlug.length > b.srcSlug.length) return +1; else if (a.name.length < b.name.length) return -1; else if (a.name.length > b.name.length) return +1; else if (a.srcSlug < b.srcSlug) return -1; else if (a.srcSlug > b.srcSlug) return +1; else return 0; }); if (matches.length === 0) { return [null, "no matches"]; } else { return [matches.slice(0, 16), null]; } }, [gameIndex?.data, debouncedTargetNameOrSuggestion]); const input = useRef(null); useEffect(() => { setTimeout(() => { const element = input.current; if (!element) { return; } element.focus(); }, 0); }, []); useProgressIndicator(home.loading); useProgressIndicator(gameIndex.loading); const backendVersion = home?.data?.stats?.version; const backendVersionLink = !backendVersion || backendVersion?.endsWith("-dev") ? "https://github.com/jeremyBanks/speedruns/" : backendVersion.includes("-dev") ? `https://github.com/jeremyBanks/speedruns/releases/tag/${backendVersion}` : `https://crates.io/crates/speedruns/${backendVersion}`; const frontendVersionLink = !frontendVersion || frontendVersion?.endsWith("-dev") ? "https://github.com/jeremyBanks/speedruns/" : frontendVersion.includes("-dev") ? `https://github.com/jeremyBanks/speedruns/releases/tag/${frontendVersion}` : `https://www.npmjs.com/package/speedruns/v/${frontendVersion}`; return (
speedruns.ca {gameIndex?.error || home?.error ? (
{JSON.stringify([gameIndex?.error, home?.error], null, 2)}
) : null}

Games

{ event.preventDefault(); // HACK: sue me (event.target as any) .closest("section") .querySelector("ul a") ?.click(); }} >
{gameIndex?.data ? ( <> {targetGames ? ( ) : ( <>

Search failed: {orError}

)} ) : ( )}

Internals

{home?.data ? ( <>

Stats

  • updated:{" "} {new Date(home.data.stats.lastUpdated) .toISOString() .slice(0, "YYYY-MM-DD".length)}
  • games: {home.data.stats.games}
  • runs: {home.data.stats.runs}
) : null}

GraphQL Schema

); }; export default withApollo(HomePage); const GetHomeStats = gql` query GetHomeStats { stats { lastUpdated runs games version } seed } `; const GetGameIndex = gql` query GetGameIndex { games { id srcSlug name } } `;