File size: 5,068 Bytes
8a37195
 
 
9f9a669
 
8a37195
a990603
 
8a37195
a990603
 
 
8a37195
 
 
 
9f9a669
 
3c85a10
8a37195
 
 
 
 
 
 
 
 
 
9f9a669
 
3c85a10
8a37195
9f9a669
 
 
 
 
 
 
8a37195
 
6346799
8a37195
6346799
 
 
8a37195
 
6346799
 
 
 
8a37195
 
9f9a669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a37195
6346799
8a37195
9f9a669
291477f
 
 
 
3c85a10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9f9a669
 
 
 
 
 
 
 
 
 
 
8a37195
 
 
 
9f9a669
8a37195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6346799
8a37195
6346799
 
8a37195
 
6346799
8a37195
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"use client";

import Link from "next/link";
import React, { useMemo, useState } from "react";
import { useFlaggedEpisodes } from "@/context/flagged-episodes-context";

import type { DatasetDisplayInfo } from "@/app/[org]/[dataset]/[episode]/fetch-data";

interface SidebarProps {
  datasetInfo: DatasetDisplayInfo;
  paginatedEpisodes: number[];
  episodeId: number;
  totalPages: number;
  currentPage: number;
  prevPage: () => void;
  nextPage: () => void;
  showFlaggedOnly: boolean;
  onShowFlaggedOnlyChange: (v: boolean) => void;
  onEpisodeSelect?: (ep: number) => void;
}

const Sidebar: React.FC<SidebarProps> = ({
  datasetInfo,
  paginatedEpisodes,
  episodeId,
  totalPages,
  currentPage,
  prevPage,
  nextPage,
  showFlaggedOnly,
  onShowFlaggedOnlyChange,
  onEpisodeSelect,
}) => {
  const [mobileVisible, setMobileVisible] = useState(false);
  const { flagged, count, toggle } = useFlaggedEpisodes();

  const displayEpisodes = useMemo(() => {
    if (!showFlaggedOnly || count === 0) return paginatedEpisodes;
    return [...flagged].sort((a, b) => a - b);
  }, [paginatedEpisodes, showFlaggedOnly, flagged, count]);

  return (
    <div className="flex z-10 shrink-0">
      <nav
        className={`shrink-0 overflow-y-auto bg-slate-900 p-5 break-words w-60 ${
          mobileVisible ? "block" : "hidden"
        } md:block`}
        aria-label="Sidebar navigation"
      >
        <ul className="text-sm text-slate-300 space-y-0.5">
          <li>Frames: {datasetInfo.total_frames.toLocaleString()}</li>
          <li>Episodes: {datasetInfo.total_episodes.toLocaleString()}</li>
          <li>FPS: {datasetInfo.fps}</li>
        </ul>

        <div className="mt-4 flex items-center justify-between">
          <p className="text-sm font-semibold text-slate-200">Episodes:</p>
          {count > 0 && (
            <button
              onClick={() => onShowFlaggedOnlyChange(!showFlaggedOnly)}
              className={`text-xs px-1.5 py-0.5 rounded transition-colors ${
                showFlaggedOnly
                  ? "bg-orange-500/20 text-orange-400 border border-orange-500/40"
                  : "text-slate-500 hover:text-slate-300 border border-slate-700"
              }`}
            >
              Flagged ({count})
            </button>
          )}
        </div>

        <div className="ml-2 mt-1">
          <ul>
            {displayEpisodes.map((episode) => (
              <li
                key={episode}
                className="mt-0.5 font-mono text-sm flex items-center gap-1"
              >
                {onEpisodeSelect ? (
                  <button
                    onClick={() => onEpisodeSelect(episode)}
                    className={`underline text-left cursor-pointer ${episode === episodeId ? "-ml-1 font-bold text-orange-400" : ""}`}
                  >
                    Episode {episode}
                  </button>
                ) : (
                  <Link
                    href={`./episode_${episode}`}
                    className={`underline ${episode === episodeId ? "-ml-1 font-bold" : ""}`}
                  >
                    Episode {episode}
                  </Link>
                )}
                <button
                  onClick={() => toggle(episode)}
                  className={`text-xs leading-none px-0.5 rounded transition-colors ${
                    flagged.has(episode)
                      ? "text-orange-400 hover:text-orange-300"
                      : "text-slate-600 hover:text-slate-400"
                  }`}
                  title={flagged.has(episode) ? "Unflag" : "Flag"}
                >

                </button>
              </li>
            ))}
          </ul>

          {!showFlaggedOnly && totalPages > 1 && (
            <div className="mt-3 flex items-center text-xs">
              <button
                onClick={prevPage}
                className={`mr-2 rounded bg-slate-800 px-2 py-1 ${
                  currentPage === 1 ? "cursor-not-allowed opacity-50" : ""
                }`}
                disabled={currentPage === 1}
              >
                « Prev
              </button>
              <span className="mr-2 font-mono">
                {currentPage} / {totalPages}
              </span>
              <button
                onClick={nextPage}
                className={`rounded bg-slate-800 px-2 py-1 ${
                  currentPage === totalPages
                    ? "cursor-not-allowed opacity-50"
                    : ""
                }`}
                disabled={currentPage === totalPages}
              >
                Next »
              </button>
            </div>
          )}
        </div>
      </nav>

      <button
        className="mx-1 flex items-center opacity-50 hover:opacity-100 focus:outline-none focus:ring-0 md:hidden"
        onClick={() => setMobileVisible((prev) => !prev)}
        title="Toggle sidebar"
      >
        <div className="h-10 w-2 rounded-full bg-slate-500" />
      </button>
    </div>
  );
};

export default Sidebar;