| <script lang="ts"> | |
| import Leaderboard from "./Leaderboard.svelte"; | |
| import ModelDetails from "./ModelDetails.svelte"; | |
| import Viewer from "./Viewer.svelte"; | |
| import Vote from "./Vote.svelte"; | |
| import About from "./About.svelte"; | |
| import { Filter, CheckmarkOutline } from "carbon-icons-svelte"; | |
| import { onMount } from "svelte"; | |
| import { CaretDown, Code } from "carbon-icons-svelte"; | |
| interface Scene { | |
| name: string; | |
| url: string; | |
| thumbnail: string; | |
| } | |
| let currentView: "Leaderboard" | "Vote" | "ModelDetails" | "Viewer" | "About" = "Vote"; | |
| let selectedEntry: { name: string } | null = null; | |
| let selectedScene: Scene | null = null; | |
| let showOnlyOpenSource = false; | |
| let showFilter = false; | |
| let filterContainer: HTMLDivElement; | |
| function handleClickOutside(event: MouseEvent) { | |
| if (filterContainer && !filterContainer.contains(event.target as Node)) { | |
| showFilter = false; | |
| } | |
| } | |
| onMount(() => { | |
| document.addEventListener("click", handleClickOutside); | |
| return () => { | |
| document.removeEventListener("click", handleClickOutside); | |
| }; | |
| }); | |
| function goHome() { | |
| window.location.href = "/"; | |
| } | |
| function showModelDetails(entry: { name: string }) { | |
| selectedEntry = entry; | |
| currentView = "ModelDetails"; | |
| } | |
| function showScene(scene: Scene) { | |
| selectedScene = scene; | |
| currentView = "Viewer"; | |
| } | |
| </script> | |
| <div class="container"> | |
| <div on:pointerdown={goHome} class="banner"> | |
| <h1>3D Arena</h1> | |
| <p>Generative 3D Leaderboard</p> | |
| </div> | |
| {#if currentView === "Leaderboard" || currentView === "Vote" || currentView === "About"} | |
| <div class="tabs"> | |
| <button on:click={() => (currentView = "Vote")} class={currentView === "Vote" ? "active" : ""}>Vote</button> | |
| <button on:click={() => (currentView = "Leaderboard")} class={currentView === "Leaderboard" ? "active" : ""} | |
| >Leaderboard</button | |
| > | |
| <button on:click={() => (currentView = "About")} class={currentView === "About" ? "active" : ""} | |
| >About</button | |
| > | |
| {#if currentView === "Leaderboard"} | |
| <div class="filter-container" bind:this={filterContainer}> | |
| <button | |
| class="filter-button" | |
| on:click={() => (showFilter = !showFilter)} | |
| aria-expanded={showFilter} | |
| aria-haspopup="true" | |
| > | |
| <Filter size={20} /> | |
| <CaretDown size={16} class="caret" /> | |
| </button> | |
| {#if showFilter} | |
| <div class="filter-dropdown" role="menu" aria-label="Filter options"> | |
| <div class="filter-section"> | |
| <div class="filter-section-title">Filter Options</div> | |
| <div | |
| class="filter-option {showOnlyOpenSource ? 'active' : ''}" | |
| on:click={() => (showOnlyOpenSource = !showOnlyOpenSource)} | |
| on:keydown={(e) => e.key === "Enter" && (showOnlyOpenSource = !showOnlyOpenSource)} | |
| role="menuitemcheckbox" | |
| aria-checked={showOnlyOpenSource} | |
| tabindex="0" | |
| > | |
| <div class="filter-label"> | |
| <Code size={16} class="filter-icon" /> | |
| Open source | |
| </div> | |
| <span class="filter-checkbox">✓</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if currentView === "Leaderboard"} | |
| <Leaderboard onEntryClick={showModelDetails} {showOnlyOpenSource} /> | |
| {:else if currentView === "Vote"} | |
| <Vote /> | |
| {:else if currentView === "ModelDetails" && selectedEntry} | |
| <ModelDetails | |
| modelName={selectedEntry.name} | |
| onBack={() => (currentView = "Leaderboard")} | |
| onSceneClick={showScene} | |
| /> | |
| {:else if currentView === "Viewer" && selectedScene && selectedEntry} | |
| <Viewer modelName={selectedEntry.name} scene={selectedScene} onBack={() => (currentView = "ModelDetails")} /> | |
| {:else if currentView === "About"} | |
| <About /> | |
| {/if} | |
| </div> | |