Spaces:
Running
Running
dylanebert
commited on
Commit
·
5e88463
1
Parent(s):
5679415
open source filter option
Browse files- src/routes/+page.svelte +54 -1
- src/routes/Leaderboard.svelte +16 -3
- static/global.css +116 -0
src/routes/+page.svelte
CHANGED
@@ -4,6 +4,9 @@
|
|
4 |
import Viewer from "./Viewer.svelte";
|
5 |
import Vote from "./Vote.svelte";
|
6 |
import About from "./About.svelte";
|
|
|
|
|
|
|
7 |
|
8 |
interface Scene {
|
9 |
name: string;
|
@@ -14,6 +17,22 @@
|
|
14 |
let currentView: "Leaderboard" | "Vote" | "ModelDetails" | "Viewer" | "About" = "Vote";
|
15 |
let selectedEntry: { name: string } | null = null;
|
16 |
let selectedScene: Scene | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
function goHome() {
|
19 |
window.location.href = "/";
|
@@ -45,11 +64,45 @@
|
|
45 |
<button on:click={() => (currentView = "About")} class={currentView === "About" ? "active" : ""}
|
46 |
>About</button
|
47 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
</div>
|
49 |
{/if}
|
50 |
|
51 |
{#if currentView === "Leaderboard"}
|
52 |
-
<Leaderboard onEntryClick={showModelDetails} />
|
53 |
{:else if currentView === "Vote"}
|
54 |
<Vote />
|
55 |
{:else if currentView === "ModelDetails" && selectedEntry}
|
|
|
4 |
import Viewer from "./Viewer.svelte";
|
5 |
import Vote from "./Vote.svelte";
|
6 |
import About from "./About.svelte";
|
7 |
+
import { Filter, CheckmarkOutline } from "carbon-icons-svelte";
|
8 |
+
import { onMount } from "svelte";
|
9 |
+
import { CaretDown, Code } from "carbon-icons-svelte";
|
10 |
|
11 |
interface Scene {
|
12 |
name: string;
|
|
|
17 |
let currentView: "Leaderboard" | "Vote" | "ModelDetails" | "Viewer" | "About" = "Vote";
|
18 |
let selectedEntry: { name: string } | null = null;
|
19 |
let selectedScene: Scene | null = null;
|
20 |
+
let showOnlyOpenSource = false;
|
21 |
+
let showFilter = false;
|
22 |
+
let filterContainer: HTMLDivElement;
|
23 |
+
|
24 |
+
function handleClickOutside(event: MouseEvent) {
|
25 |
+
if (filterContainer && !filterContainer.contains(event.target as Node)) {
|
26 |
+
showFilter = false;
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
onMount(() => {
|
31 |
+
document.addEventListener("click", handleClickOutside);
|
32 |
+
return () => {
|
33 |
+
document.removeEventListener("click", handleClickOutside);
|
34 |
+
};
|
35 |
+
});
|
36 |
|
37 |
function goHome() {
|
38 |
window.location.href = "/";
|
|
|
64 |
<button on:click={() => (currentView = "About")} class={currentView === "About" ? "active" : ""}
|
65 |
>About</button
|
66 |
>
|
67 |
+
{#if currentView === "Leaderboard"}
|
68 |
+
<div class="filter-container" bind:this={filterContainer}>
|
69 |
+
<button
|
70 |
+
class="filter-button"
|
71 |
+
on:click={() => (showFilter = !showFilter)}
|
72 |
+
aria-expanded={showFilter}
|
73 |
+
aria-haspopup="true"
|
74 |
+
>
|
75 |
+
<Filter size={20} />
|
76 |
+
<CaretDown size={16} class="caret" />
|
77 |
+
</button>
|
78 |
+
{#if showFilter}
|
79 |
+
<div class="filter-dropdown" role="menu" aria-label="Filter options">
|
80 |
+
<div class="filter-section">
|
81 |
+
<div class="filter-section-title">Filter Options</div>
|
82 |
+
<div
|
83 |
+
class="filter-option {showOnlyOpenSource ? 'active' : ''}"
|
84 |
+
on:click={() => (showOnlyOpenSource = !showOnlyOpenSource)}
|
85 |
+
on:keydown={(e) => e.key === "Enter" && (showOnlyOpenSource = !showOnlyOpenSource)}
|
86 |
+
role="menuitemcheckbox"
|
87 |
+
aria-checked={showOnlyOpenSource}
|
88 |
+
tabindex="0"
|
89 |
+
>
|
90 |
+
<div class="filter-label">
|
91 |
+
<Code size={16} class="filter-icon" />
|
92 |
+
Open source
|
93 |
+
</div>
|
94 |
+
<span class="filter-checkbox">✓</span>
|
95 |
+
</div>
|
96 |
+
</div>
|
97 |
+
</div>
|
98 |
+
{/if}
|
99 |
+
</div>
|
100 |
+
{/if}
|
101 |
</div>
|
102 |
{/if}
|
103 |
|
104 |
{#if currentView === "Leaderboard"}
|
105 |
+
<Leaderboard onEntryClick={showModelDetails} {showOnlyOpenSource} />
|
106 |
{:else if currentView === "Vote"}
|
107 |
<Vote />
|
108 |
{:else if currentView === "ModelDetails" && selectedEntry}
|
src/routes/Leaderboard.svelte
CHANGED
@@ -8,13 +8,16 @@
|
|
8 |
rank: number;
|
9 |
score: number;
|
10 |
votes: number;
|
|
|
11 |
displayName?: string;
|
12 |
}
|
13 |
|
14 |
export let onEntryClick: (entry: Entry) => void;
|
|
|
15 |
|
16 |
const baseUrl = "https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/outputs";
|
17 |
let leaderboard: Entry[] = [];
|
|
|
18 |
|
19 |
const fetchLeaderboardData = async () => {
|
20 |
const url = "/api/leaderboard";
|
@@ -34,19 +37,29 @@
|
|
34 |
entriesWithDisplayNames.sort((a, b) => a.rank - b.rank);
|
35 |
|
36 |
leaderboard = entriesWithDisplayNames;
|
|
|
37 |
};
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
onMount(async () => {
|
40 |
await fetchLeaderboardData();
|
41 |
});
|
42 |
</script>
|
43 |
|
44 |
-
{#if
|
45 |
<div class="grid">
|
46 |
-
{#each
|
47 |
<button class="grid-item" on:click={() => onEntryClick(entry)}>
|
48 |
<img src={`${baseUrl}/${entry.name}/thumbnail.png`} alt={entry.name} class="thumbnail" />
|
49 |
-
<div class="ranking">{
|
50 |
<div class="title">{entry.displayName}</div>
|
51 |
<div class="score-container">
|
52 |
<div class="score">
|
|
|
8 |
rank: number;
|
9 |
score: number;
|
10 |
votes: number;
|
11 |
+
open_source: boolean;
|
12 |
displayName?: string;
|
13 |
}
|
14 |
|
15 |
export let onEntryClick: (entry: Entry) => void;
|
16 |
+
export let showOnlyOpenSource: boolean;
|
17 |
|
18 |
const baseUrl = "https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/outputs";
|
19 |
let leaderboard: Entry[] = [];
|
20 |
+
let filteredLeaderboard: Entry[] = [];
|
21 |
|
22 |
const fetchLeaderboardData = async () => {
|
23 |
const url = "/api/leaderboard";
|
|
|
37 |
entriesWithDisplayNames.sort((a, b) => a.rank - b.rank);
|
38 |
|
39 |
leaderboard = entriesWithDisplayNames;
|
40 |
+
updateFilteredLeaderboard();
|
41 |
};
|
42 |
|
43 |
+
const updateFilteredLeaderboard = () => {
|
44 |
+
filteredLeaderboard = showOnlyOpenSource ? leaderboard.filter((entry) => entry.open_source) : leaderboard;
|
45 |
+
};
|
46 |
+
|
47 |
+
$: {
|
48 |
+
showOnlyOpenSource;
|
49 |
+
updateFilteredLeaderboard();
|
50 |
+
}
|
51 |
+
|
52 |
onMount(async () => {
|
53 |
await fetchLeaderboardData();
|
54 |
});
|
55 |
</script>
|
56 |
|
57 |
+
{#if filteredLeaderboard.length > 0}
|
58 |
<div class="grid">
|
59 |
+
{#each filteredLeaderboard as entry, index}
|
60 |
<button class="grid-item" on:click={() => onEntryClick(entry)}>
|
61 |
<img src={`${baseUrl}/${entry.name}/thumbnail.png`} alt={entry.name} class="thumbnail" />
|
62 |
+
<div class="ranking">{index + 1}</div>
|
63 |
<div class="title">{entry.displayName}</div>
|
64 |
<div class="score-container">
|
65 |
<div class="score">
|
static/global.css
CHANGED
@@ -476,6 +476,7 @@ body {
|
|
476 |
margin-bottom: 10px;
|
477 |
gap: 10px;
|
478 |
width: 100%;
|
|
|
479 |
}
|
480 |
|
481 |
.tabs button {
|
@@ -498,3 +499,118 @@ body {
|
|
498 |
.tabs button.active {
|
499 |
background-color: #444;
|
500 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
476 |
margin-bottom: 10px;
|
477 |
gap: 10px;
|
478 |
width: 100%;
|
479 |
+
align-items: center;
|
480 |
}
|
481 |
|
482 |
.tabs button {
|
|
|
499 |
.tabs button.active {
|
500 |
background-color: #444;
|
501 |
}
|
502 |
+
|
503 |
+
.filter-container {
|
504 |
+
position: relative;
|
505 |
+
margin-left: auto;
|
506 |
+
}
|
507 |
+
|
508 |
+
.filter-button {
|
509 |
+
background-color: #333;
|
510 |
+
border: 1px solid #444;
|
511 |
+
color: #fff;
|
512 |
+
padding: 8px 12px;
|
513 |
+
cursor: pointer;
|
514 |
+
display: flex;
|
515 |
+
align-items: center;
|
516 |
+
justify-content: center;
|
517 |
+
border-radius: 4px;
|
518 |
+
transition: background-color 0.2s ease;
|
519 |
+
gap: 4px;
|
520 |
+
font-size: 14px;
|
521 |
+
}
|
522 |
+
|
523 |
+
.filter-button:hover {
|
524 |
+
background-color: #444;
|
525 |
+
}
|
526 |
+
|
527 |
+
.filter-button .caret {
|
528 |
+
transition: transform 0.2s ease;
|
529 |
+
margin-left: 2px;
|
530 |
+
}
|
531 |
+
|
532 |
+
.filter-button[aria-expanded="true"] .caret {
|
533 |
+
transform: rotate(180deg);
|
534 |
+
}
|
535 |
+
|
536 |
+
.filter-dropdown {
|
537 |
+
position: absolute;
|
538 |
+
top: calc(100% + 5px);
|
539 |
+
right: 0;
|
540 |
+
background-color: #1a1b1e;
|
541 |
+
border: 1px solid #444;
|
542 |
+
border-radius: 4px;
|
543 |
+
padding: 4px;
|
544 |
+
min-width: 200px;
|
545 |
+
z-index: 1000;
|
546 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
547 |
+
}
|
548 |
+
|
549 |
+
.filter-option {
|
550 |
+
display: flex;
|
551 |
+
align-items: center;
|
552 |
+
justify-content: space-between;
|
553 |
+
padding: 8px 12px;
|
554 |
+
color: #ddd;
|
555 |
+
cursor: pointer;
|
556 |
+
border-radius: 2px;
|
557 |
+
white-space: nowrap;
|
558 |
+
font-size: 14px;
|
559 |
+
transition: all 0.2s ease;
|
560 |
+
background-color: transparent;
|
561 |
+
position: relative;
|
562 |
+
}
|
563 |
+
|
564 |
+
.filter-option:hover {
|
565 |
+
background-color: #2a2d31;
|
566 |
+
}
|
567 |
+
|
568 |
+
.filter-option.active {
|
569 |
+
background-color: #333;
|
570 |
+
}
|
571 |
+
|
572 |
+
.filter-label {
|
573 |
+
display: flex;
|
574 |
+
align-items: center;
|
575 |
+
gap: 8px;
|
576 |
+
}
|
577 |
+
|
578 |
+
.filter-icon {
|
579 |
+
color: #888;
|
580 |
+
transition: color 0.2s ease;
|
581 |
+
}
|
582 |
+
|
583 |
+
.filter-option:hover .filter-icon,
|
584 |
+
.filter-option.active .filter-icon {
|
585 |
+
color: #ddd;
|
586 |
+
}
|
587 |
+
|
588 |
+
.filter-checkbox {
|
589 |
+
position: absolute;
|
590 |
+
right: 12px;
|
591 |
+
color: transparent;
|
592 |
+
transition: color 0.2s ease;
|
593 |
+
font-size: 14px;
|
594 |
+
}
|
595 |
+
|
596 |
+
.filter-option.active .filter-checkbox {
|
597 |
+
color: #fff;
|
598 |
+
}
|
599 |
+
|
600 |
+
.filter-section {
|
601 |
+
padding: 8px 0;
|
602 |
+
border-bottom: 1px solid #333;
|
603 |
+
}
|
604 |
+
|
605 |
+
.filter-section:last-child {
|
606 |
+
border-bottom: none;
|
607 |
+
}
|
608 |
+
|
609 |
+
.filter-section-title {
|
610 |
+
padding: 4px 12px;
|
611 |
+
color: #888;
|
612 |
+
font-size: 12px;
|
613 |
+
text-transform: uppercase;
|
614 |
+
letter-spacing: 0.5px;
|
615 |
+
margin-bottom: 4px;
|
616 |
+
}
|