Spaces:
Running
Running
| <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 } from "carbon-icons-svelte"; | |
| import { onMount } from "svelte"; | |
| import { CaretDown, Code, Development } from "carbon-icons-svelte"; | |
| import { dev } from "$app/environment"; | |
| import { getApiConfig } from "$lib/config"; | |
| 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; | |
| let voteType: "render" | "topology" = "render"; | |
| $: apiConfig = getApiConfig(); | |
| $: isDevelopment = dev || apiConfig.isLocal; | |
| 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"> | |
| {#if isDevelopment} | |
| <div class="dev-banner"> | |
| <Development size={16} /> | |
| <span>Development Mode - Backend: {apiConfig.baseUrl}</span> | |
| </div> | |
| {/if} | |
| <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">Vote Type</div> | |
| <div | |
| class="filter-option {voteType === 'render' ? 'active' : ''}" | |
| on:click={() => (voteType = "render")} | |
| on:keydown={(e) => e.key === "Enter" && (voteType = "render")} | |
| role="menuitemradio" | |
| aria-checked={voteType === "render"} | |
| tabindex="0" | |
| > | |
| <div class="filter-label">Render</div> | |
| <span class="filter-checkbox">✓</span> | |
| </div> | |
| <div | |
| class="filter-option {voteType === 'topology' ? 'active' : ''}" | |
| on:click={() => (voteType = "topology")} | |
| on:keydown={(e) => e.key === "Enter" && (voteType = "topology")} | |
| role="menuitemradio" | |
| aria-checked={voteType === "topology"} | |
| tabindex="0" | |
| > | |
| <div class="filter-label">Topology</div> | |
| <span class="filter-checkbox">✓</span> | |
| </div> | |
| </div> | |
| <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} {voteType} /> | |
| {: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> | |