Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): allow presets to be explored in dedicated page #1232

Merged
merged 9 commits into from
Jun 18, 2024
Merged
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
12 changes: 12 additions & 0 deletions frontend/src/app/(navfooter)/preset/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Presets } from "@/app/(navfooter)/preset/presets"
import { get } from "@/lib/api/request"

export default async function Page() {
const compilers = await get("/compiler")

return (
<main className="mx-auto w-full max-w-3xl p-4">
<Presets serverCompilers={compilers}/>
</main>
)
}
36 changes: 36 additions & 0 deletions frontend/src/app/(navfooter)/preset/presets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client"

import { useState } from "react"

import PlatformSelect from "@/components/PlatformSelect"
import { PresetList } from "@/components/PresetList"
import * as api from "@/lib/api"

export function Presets({ serverCompilers }: {
serverCompilers: {
platforms: {
[id: string]: api.Platform
}
compilers: {
[id: string]: api.Compiler
}
}
}) {

const platforms = Object.keys(serverCompilers.platforms)

const [platform, setPlatform] = useState<string>(platforms.length > 0 ? platforms[0] : "")

return (
<section>
<h2 className="pb-2 text-lg font-medium tracking-tight">Platforms</h2>
<PlatformSelect
platforms={serverCompilers.platforms}
value={platform}
onChange={setPlatform}
/>
<h2 className="py-2 text-lg font-medium tracking-tight">Presets</h2>
<PresetList url={`/preset?platform=${platform}`}/>
</section>
)
}
20 changes: 18 additions & 2 deletions frontend/src/components/PlatformSelect/PlatformIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import Link from "next/link"

import { platformUrl } from "@/lib/api/urls"

import LogoDreamcast from "./dreamcast.svg"
import LogoGBA from "./gba.svg"
import LogoGCWii from "./gc_wii.svg"
Expand Down Expand Up @@ -39,15 +43,27 @@ export const PLATFORMS = Object.keys(ICONS)
export type Props = {
platform: string
className?: string
clickable?: boolean
size?: string | number
}

export function platformIcon(platform: string) {
return ICONS[platform as keyof typeof ICONS] || UnknownIcon
}

export function PlatformIcon({ platform, className, size }: Props) {
export function PlatformIcon({ platform, className, clickable, size }: Props) {
const Icon = platformIcon(platform)
const url = platformUrl(platform)

return <Icon width={size} height={size} className={className} />
if (clickable) {
return (
<Link href={url}>
<Icon width={size} height={size} className={className} />
</Link>
)
} else {
return (
<Icon width={size} height={size} className={className} />
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function PlatformSelect({ platforms, value, onChange, className }
className={classNames(styles.platform, { [styles.selected]: value === key })}
onClick={() => onChange(key)}
>
<PlatformIcon platform={key} />
<PlatformIcon clickable={false} platform={key} />
<div className={styles.labelContainer}>
<div className={styles.consoleName}>{platform.name}</div>
<div className={styles.platformName}>{platform.description}</div>
Expand Down
73 changes: 73 additions & 0 deletions frontend/src/components/PresetList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client"

import type { ReactNode, JSX } from "react"

import Link from "next/link"

import classNames from "classnames"

import AsyncButton from "@/components/AsyncButton"
import Button from "@/components/Button"
import LoadingSpinner from "@/components/loading.svg"
import { PlatformIcon } from "@/components/PlatformSelect/PlatformIcon"
import { type Preset, usePaginated } from "@/lib/api"
import { presetUrl } from "@/lib/api/urls"
import useTranslation from "@/lib/i18n/translate"

export interface Props {
url?: string
className?: string
item?: ({ preset }: { preset: Preset }) => JSX.Element
emptyButtonLabel?: ReactNode
}

export function PresetList({ url, className, item, emptyButtonLabel }: Props): JSX.Element {
const { results, isLoading, hasNext, loadNext } = usePaginated<Preset>(url || "/preset")
if (results.length === 0 && isLoading) {
return <div className={classNames("flex justify-center items-center gap-[0.5em] p-[1em] opacity-50", className)}>
<LoadingSpinner width="1.5em" height="1.5em" />
Just a moment...
</div>
}

const Item = item ?? PresetItem

return (
<ul className={classNames("flex flex-col justify-center gap-[0.5em] overflow-hidden rounded-md border-gray-6 text-sm", className)}>
{results.map(preset => (
<Item hideIcon key={preset.id} preset={preset} />
))}
{results.length === 0 && emptyButtonLabel && <li className={"col-[span_var(--num-columns,_1)] mt-[0.5em] flex items-center justify-center opacity-70"}>
<Link href="/new">

<Button>
{emptyButtonLabel}
</Button>

</Link>
</li>}
{hasNext && <li className={"col-[span_var(--num-columns,_1)] mt-[0.5em] flex items-center justify-center opacity-70"}>
<AsyncButton onClick={loadNext}>
Show more
</AsyncButton>
</li>}
</ul>
)
}

export function PresetItem({ preset, hideIcon }: { preset: Preset, hideIcon?: boolean }): JSX.Element {
const compilersTranslation = useTranslation("compilers")
const compilerName = compilersTranslation.t(preset.compiler)

return (
<div className="rounded-md border border-gray-6 p-[1em] text-sm">
<div className="flex items-center gap-2">
{hideIcon && <PlatformIcon platform={preset.platform} className="w-[1.2em]"/>}
<a className="font-semibold hover:text-[var(--link)]" href={presetUrl(preset)}>
{preset.name}
</a>
</div>
<p className="text-gray-11">{compilerName}</p>
</div>
)
}