Spaces:
Sleeping
Sleeping
Sync from GitHub via hub-sync
Browse files
src/app/[org]/[dataset]/[episode]/episode-viewer.tsx
CHANGED
|
@@ -138,9 +138,16 @@ function EpisodeViewerInner({
|
|
| 138 |
|
| 139 |
const [videosReady, setVideosReady] = useState(!videosInfo.length);
|
| 140 |
const [chartsReady, setChartsReady] = useState(false);
|
| 141 |
-
const isLoading = !videosReady || !chartsReady;
|
| 142 |
|
| 143 |
const loadStartRef = useRef(performance.now());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
useEffect(() => {
|
| 145 |
if (!isLoading) {
|
| 146 |
console.log(
|
|
@@ -148,12 +155,6 @@ function EpisodeViewerInner({
|
|
| 148 |
);
|
| 149 |
}
|
| 150 |
}, [isLoading, videosReady, chartsReady]);
|
| 151 |
-
|
| 152 |
-
const router = useRouter();
|
| 153 |
-
const searchParams = useSearchParams();
|
| 154 |
-
|
| 155 |
-
// Tab state & lazy stats
|
| 156 |
-
const [activeTab, setActiveTab] = useState<ActiveTab>("episodes");
|
| 157 |
const [, setColumnMinMax] = useState<ColumnMinMax[] | null>(null);
|
| 158 |
const [episodeLengthStats, setEpisodeLengthStats] =
|
| 159 |
useState<EpisodeLengthStats | null>(null);
|
|
@@ -321,6 +322,11 @@ function EpisodeViewerInner({
|
|
| 321 |
// Use context for time sync
|
| 322 |
const { currentTime, setCurrentTime, setIsPlaying, isPlaying } = useTime();
|
| 323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
// Pagination state
|
| 325 |
const pageSize = 100;
|
| 326 |
const [currentPage, setCurrentPage] = useState(1);
|
|
@@ -536,18 +542,26 @@ function EpisodeViewerInner({
|
|
| 536 |
|
| 537 |
{/* Body: sidebar + content */}
|
| 538 |
<div className="flex flex-1 min-h-0">
|
| 539 |
-
{/* Sidebar —
|
| 540 |
-
{activeTab === "episodes" && (
|
| 541 |
<Sidebar
|
| 542 |
datasetInfo={datasetInfo}
|
| 543 |
paginatedEpisodes={paginatedEpisodes}
|
| 544 |
-
episodeId={episodeId}
|
| 545 |
totalPages={totalPages}
|
| 546 |
currentPage={currentPage}
|
| 547 |
prevPage={prevPage}
|
| 548 |
nextPage={nextPage}
|
| 549 |
showFlaggedOnly={sidebarFlaggedOnly}
|
| 550 |
onShowFlaggedOnlyChange={setSidebarFlaggedOnly}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
/>
|
| 552 |
)}
|
| 553 |
|
|
@@ -675,7 +689,12 @@ function EpisodeViewerInner({
|
|
| 675 |
|
| 676 |
{activeTab === "urdf" && (
|
| 677 |
<Suspense fallback={<Loading />}>
|
| 678 |
-
<URDFViewer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 679 |
</Suspense>
|
| 680 |
)}
|
| 681 |
</div>
|
|
|
|
| 138 |
|
| 139 |
const [videosReady, setVideosReady] = useState(!videosInfo.length);
|
| 140 |
const [chartsReady, setChartsReady] = useState(false);
|
|
|
|
| 141 |
|
| 142 |
const loadStartRef = useRef(performance.now());
|
| 143 |
+
|
| 144 |
+
const router = useRouter();
|
| 145 |
+
const searchParams = useSearchParams();
|
| 146 |
+
|
| 147 |
+
// Tab state & lazy stats
|
| 148 |
+
const [activeTab, setActiveTab] = useState<ActiveTab>("episodes");
|
| 149 |
+
const isLoading = activeTab === "episodes" && (!videosReady || !chartsReady);
|
| 150 |
+
|
| 151 |
useEffect(() => {
|
| 152 |
if (!isLoading) {
|
| 153 |
console.log(
|
|
|
|
| 155 |
);
|
| 156 |
}
|
| 157 |
}, [isLoading, videosReady, chartsReady]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
const [, setColumnMinMax] = useState<ColumnMinMax[] | null>(null);
|
| 159 |
const [episodeLengthStats, setEpisodeLengthStats] =
|
| 160 |
useState<EpisodeLengthStats | null>(null);
|
|
|
|
| 322 |
// Use context for time sync
|
| 323 |
const { currentTime, setCurrentTime, setIsPlaying, isPlaying } = useTime();
|
| 324 |
|
| 325 |
+
// URDFViewer episode changer — populated by URDFViewer on mount
|
| 326 |
+
const urdfChangerRef = useRef<((ep: number) => void) | undefined>(undefined);
|
| 327 |
+
const [urdfEpisode, setUrdfEpisode] = useState(episodeId);
|
| 328 |
+
useEffect(() => setUrdfEpisode(episodeId), [episodeId]);
|
| 329 |
+
|
| 330 |
// Pagination state
|
| 331 |
const pageSize = 100;
|
| 332 |
const [currentPage, setCurrentPage] = useState(1);
|
|
|
|
| 542 |
|
| 543 |
{/* Body: sidebar + content */}
|
| 544 |
<div className="flex flex-1 min-h-0">
|
| 545 |
+
{/* Sidebar — on Episodes and 3D Replay tabs */}
|
| 546 |
+
{(activeTab === "episodes" || activeTab === "urdf") && (
|
| 547 |
<Sidebar
|
| 548 |
datasetInfo={datasetInfo}
|
| 549 |
paginatedEpisodes={paginatedEpisodes}
|
| 550 |
+
episodeId={activeTab === "urdf" ? urdfEpisode : episodeId}
|
| 551 |
totalPages={totalPages}
|
| 552 |
currentPage={currentPage}
|
| 553 |
prevPage={prevPage}
|
| 554 |
nextPage={nextPage}
|
| 555 |
showFlaggedOnly={sidebarFlaggedOnly}
|
| 556 |
onShowFlaggedOnlyChange={setSidebarFlaggedOnly}
|
| 557 |
+
onEpisodeSelect={
|
| 558 |
+
activeTab === "urdf"
|
| 559 |
+
? (ep) => {
|
| 560 |
+
setUrdfEpisode(ep);
|
| 561 |
+
urdfChangerRef.current?.(ep);
|
| 562 |
+
}
|
| 563 |
+
: undefined
|
| 564 |
+
}
|
| 565 |
/>
|
| 566 |
)}
|
| 567 |
|
|
|
|
| 689 |
|
| 690 |
{activeTab === "urdf" && (
|
| 691 |
<Suspense fallback={<Loading />}>
|
| 692 |
+
<URDFViewer
|
| 693 |
+
data={data}
|
| 694 |
+
org={org}
|
| 695 |
+
dataset={dataset}
|
| 696 |
+
episodeChangerRef={urdfChangerRef}
|
| 697 |
+
/>
|
| 698 |
</Suspense>
|
| 699 |
)}
|
| 700 |
</div>
|
src/components/side-nav.tsx
CHANGED
|
@@ -16,6 +16,7 @@ interface SidebarProps {
|
|
| 16 |
nextPage: () => void;
|
| 17 |
showFlaggedOnly: boolean;
|
| 18 |
onShowFlaggedOnlyChange: (v: boolean) => void;
|
|
|
|
| 19 |
}
|
| 20 |
|
| 21 |
const Sidebar: React.FC<SidebarProps> = ({
|
|
@@ -28,6 +29,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|
| 28 |
nextPage,
|
| 29 |
showFlaggedOnly,
|
| 30 |
onShowFlaggedOnlyChange,
|
|
|
|
| 31 |
}) => {
|
| 32 |
const [mobileVisible, setMobileVisible] = useState(false);
|
| 33 |
const { flagged, count, toggle } = useFlaggedEpisodes();
|
|
@@ -74,12 +76,21 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|
| 74 |
key={episode}
|
| 75 |
className="mt-0.5 font-mono text-sm flex items-center gap-1"
|
| 76 |
>
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
<button
|
| 84 |
onClick={() => toggle(episode)}
|
| 85 |
className={`text-xs leading-none px-0.5 rounded transition-colors ${
|
|
|
|
| 16 |
nextPage: () => void;
|
| 17 |
showFlaggedOnly: boolean;
|
| 18 |
onShowFlaggedOnlyChange: (v: boolean) => void;
|
| 19 |
+
onEpisodeSelect?: (ep: number) => void;
|
| 20 |
}
|
| 21 |
|
| 22 |
const Sidebar: React.FC<SidebarProps> = ({
|
|
|
|
| 29 |
nextPage,
|
| 30 |
showFlaggedOnly,
|
| 31 |
onShowFlaggedOnlyChange,
|
| 32 |
+
onEpisodeSelect,
|
| 33 |
}) => {
|
| 34 |
const [mobileVisible, setMobileVisible] = useState(false);
|
| 35 |
const { flagged, count, toggle } = useFlaggedEpisodes();
|
|
|
|
| 76 |
key={episode}
|
| 77 |
className="mt-0.5 font-mono text-sm flex items-center gap-1"
|
| 78 |
>
|
| 79 |
+
{onEpisodeSelect ? (
|
| 80 |
+
<button
|
| 81 |
+
onClick={() => onEpisodeSelect(episode)}
|
| 82 |
+
className={`underline text-left cursor-pointer ${episode === episodeId ? "-ml-1 font-bold text-orange-400" : ""}`}
|
| 83 |
+
>
|
| 84 |
+
Episode {episode}
|
| 85 |
+
</button>
|
| 86 |
+
) : (
|
| 87 |
+
<Link
|
| 88 |
+
href={`./episode_${episode}`}
|
| 89 |
+
className={`underline ${episode === episodeId ? "-ml-1 font-bold" : ""}`}
|
| 90 |
+
>
|
| 91 |
+
Episode {episode}
|
| 92 |
+
</Link>
|
| 93 |
+
)}
|
| 94 |
<button
|
| 95 |
onClick={() => toggle(episode)}
|
| 96 |
className={`text-xs leading-none px-0.5 rounded transition-colors ${
|
src/components/urdf-viewer.tsx
CHANGED
|
@@ -445,12 +445,16 @@ export default function URDFViewer({
|
|
| 445 |
data,
|
| 446 |
org,
|
| 447 |
dataset,
|
|
|
|
| 448 |
}: {
|
| 449 |
data: EpisodeData;
|
| 450 |
org?: string;
|
| 451 |
dataset?: string;
|
|
|
|
|
|
|
|
|
|
| 452 |
}) {
|
| 453 |
-
const { datasetInfo
|
| 454 |
const fps = datasetInfo.fps || 30;
|
| 455 |
const robotConfig = useMemo(
|
| 456 |
() => getRobotConfig(datasetInfo.robot_type),
|
|
@@ -515,6 +519,10 @@ export default function URDFViewer({
|
|
| 515 |
[ensureDatasetInfo, repoId],
|
| 516 |
);
|
| 517 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
const totalFrames = chartData.length;
|
| 519 |
|
| 520 |
// URDF joint names
|
|
@@ -711,43 +719,8 @@ export default function URDFViewer({
|
|
| 711 |
|
| 712 |
{/* Controls */}
|
| 713 |
<div className="bg-slate-800/90 border-t border-slate-700 p-3 space-y-3 shrink-0">
|
| 714 |
-
{/*
|
| 715 |
<div className="flex items-center gap-3">
|
| 716 |
-
{/* Episode selector */}
|
| 717 |
-
<div className="flex items-center gap-1.5 shrink-0">
|
| 718 |
-
<button
|
| 719 |
-
onClick={() => {
|
| 720 |
-
if (selectedEpisode > episodes[0])
|
| 721 |
-
handleEpisodeChange(selectedEpisode - 1);
|
| 722 |
-
}}
|
| 723 |
-
disabled={selectedEpisode <= episodes[0]}
|
| 724 |
-
className="w-6 h-6 flex items-center justify-center rounded bg-slate-700 hover:bg-slate-600 text-slate-300 disabled:opacity-30 disabled:cursor-not-allowed text-xs"
|
| 725 |
-
>
|
| 726 |
-
◀
|
| 727 |
-
</button>
|
| 728 |
-
<select
|
| 729 |
-
value={selectedEpisode}
|
| 730 |
-
onChange={(e) => handleEpisodeChange(Number(e.target.value))}
|
| 731 |
-
className="bg-slate-900 text-slate-200 text-xs rounded px-1.5 py-1 border border-slate-600 w-28"
|
| 732 |
-
>
|
| 733 |
-
{episodes.map((ep) => (
|
| 734 |
-
<option key={ep} value={ep}>
|
| 735 |
-
Episode {ep}
|
| 736 |
-
</option>
|
| 737 |
-
))}
|
| 738 |
-
</select>
|
| 739 |
-
<button
|
| 740 |
-
onClick={() => {
|
| 741 |
-
if (selectedEpisode < episodes[episodes.length - 1])
|
| 742 |
-
handleEpisodeChange(selectedEpisode + 1);
|
| 743 |
-
}}
|
| 744 |
-
disabled={selectedEpisode >= episodes[episodes.length - 1]}
|
| 745 |
-
className="w-6 h-6 flex items-center justify-center rounded bg-slate-700 hover:bg-slate-600 text-slate-300 disabled:opacity-30 disabled:cursor-not-allowed text-xs"
|
| 746 |
-
>
|
| 747 |
-
▶
|
| 748 |
-
</button>
|
| 749 |
-
</div>
|
| 750 |
-
|
| 751 |
{/* Play/Pause */}
|
| 752 |
<button
|
| 753 |
onClick={() => {
|
|
|
|
| 445 |
data,
|
| 446 |
org,
|
| 447 |
dataset,
|
| 448 |
+
episodeChangerRef,
|
| 449 |
}: {
|
| 450 |
data: EpisodeData;
|
| 451 |
org?: string;
|
| 452 |
dataset?: string;
|
| 453 |
+
episodeChangerRef?: React.MutableRefObject<
|
| 454 |
+
((ep: number) => void) | undefined
|
| 455 |
+
>;
|
| 456 |
}) {
|
| 457 |
+
const { datasetInfo } = data;
|
| 458 |
const fps = datasetInfo.fps || 30;
|
| 459 |
const robotConfig = useMemo(
|
| 460 |
() => getRobotConfig(datasetInfo.robot_type),
|
|
|
|
| 519 |
[ensureDatasetInfo, repoId],
|
| 520 |
);
|
| 521 |
|
| 522 |
+
useEffect(() => {
|
| 523 |
+
if (episodeChangerRef) episodeChangerRef.current = handleEpisodeChange;
|
| 524 |
+
}, [episodeChangerRef, handleEpisodeChange]);
|
| 525 |
+
|
| 526 |
const totalFrames = chartData.length;
|
| 527 |
|
| 528 |
// URDF joint names
|
|
|
|
| 719 |
|
| 720 |
{/* Controls */}
|
| 721 |
<div className="bg-slate-800/90 border-t border-slate-700 p-3 space-y-3 shrink-0">
|
| 722 |
+
{/* Timeline */}
|
| 723 |
<div className="flex items-center gap-3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
{/* Play/Pause */}
|
| 725 |
<button
|
| 726 |
onClick={() => {
|