dylanebert commited on
Commit
5e88463
·
1 Parent(s): 5679415

open source filter option

Browse files
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 leaderboard.length > 0}
45
  <div class="grid">
46
- {#each leaderboard as entry}
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">{entry.rank}</div>
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
+ }