Spaces:
Running
Running
fix lint
Browse files
src/app/[org]/[dataset]/[episode]/episode-viewer.tsx
CHANGED
|
@@ -1,6 +1,13 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import { useRouter, useSearchParams } from "next/navigation";
|
| 5 |
import { postParentMessageWithParams } from "@/utils/postParentMessage";
|
| 6 |
import { SimpleVideosPlayer } from "@/components/simple-videos-player";
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import {
|
| 4 |
+
useState,
|
| 5 |
+
useEffect,
|
| 6 |
+
useRef,
|
| 7 |
+
lazy,
|
| 8 |
+
Suspense,
|
| 9 |
+
useCallback,
|
| 10 |
+
} from "react";
|
| 11 |
import { useRouter, useSearchParams } from "next/navigation";
|
| 12 |
import { postParentMessageWithParams } from "@/utils/postParentMessage";
|
| 13 |
import { SimpleVideosPlayer } from "@/components/simple-videos-player";
|
src/app/[org]/[dataset]/[episode]/fetch-data.ts
CHANGED
|
@@ -209,7 +209,9 @@ function pickProgressColumn(rows: Record<string, unknown>[]): string | null {
|
|
| 209 |
const candidates = [...preferred, ...additionalProgressColumns];
|
| 210 |
|
| 211 |
for (const column of candidates) {
|
| 212 |
-
const hasFiniteValue = rows.some(
|
|
|
|
|
|
|
| 213 |
if (hasFiniteValue) {
|
| 214 |
return column;
|
| 215 |
}
|
|
@@ -260,13 +262,17 @@ async function loadEpisodeProgressGroup(
|
|
| 260 |
if (orderedPoints.length === 0) continue;
|
| 261 |
orderedPoints.sort((a, b) => a.order - b.order);
|
| 262 |
|
| 263 |
-
const sampledPoints = evenlySampleArray(
|
|
|
|
|
|
|
|
|
|
| 264 |
const progressKey = buildProgressSeriesKey(progressColumn);
|
| 265 |
const denominator = Math.max(sampledPoints.length - 1, 1);
|
| 266 |
const duration = Math.max(episodeDuration, 0);
|
| 267 |
|
| 268 |
return sampledPoints.map((point, idx) => ({
|
| 269 |
-
timestamp:
|
|
|
|
| 270 |
[progressKey]: point.progress,
|
| 271 |
}));
|
| 272 |
} catch {
|
|
|
|
| 209 |
const candidates = [...preferred, ...additionalProgressColumns];
|
| 210 |
|
| 211 |
for (const column of candidates) {
|
| 212 |
+
const hasFiniteValue = rows.some(
|
| 213 |
+
(row) => toFiniteNumber(row[column]) !== null,
|
| 214 |
+
);
|
| 215 |
if (hasFiniteValue) {
|
| 216 |
return column;
|
| 217 |
}
|
|
|
|
| 262 |
if (orderedPoints.length === 0) continue;
|
| 263 |
orderedPoints.sort((a, b) => a.order - b.order);
|
| 264 |
|
| 265 |
+
const sampledPoints = evenlySampleArray(
|
| 266 |
+
orderedPoints,
|
| 267 |
+
MAX_EPISODE_POINTS,
|
| 268 |
+
);
|
| 269 |
const progressKey = buildProgressSeriesKey(progressColumn);
|
| 270 |
const denominator = Math.max(sampledPoints.length - 1, 1);
|
| 271 |
const duration = Math.max(episodeDuration, 0);
|
| 272 |
|
| 273 |
return sampledPoints.map((point, idx) => ({
|
| 274 |
+
timestamp:
|
| 275 |
+
sampledPoints.length === 1 ? 0 : (idx / denominator) * duration,
|
| 276 |
[progressKey]: point.progress,
|
| 277 |
}));
|
| 278 |
} catch {
|
src/app/[org]/[dataset]/[episode]/page.tsx
CHANGED
|
@@ -25,11 +25,7 @@ export default async function EpisodePage({
|
|
| 25 |
const episodeNumber = Number(episode.replace(/^episode_/, ""));
|
| 26 |
return (
|
| 27 |
<Suspense fallback={null}>
|
| 28 |
-
<EpisodeViewer
|
| 29 |
-
org={org}
|
| 30 |
-
dataset={dataset}
|
| 31 |
-
episodeId={episodeNumber}
|
| 32 |
-
/>
|
| 33 |
</Suspense>
|
| 34 |
);
|
| 35 |
}
|
|
|
|
| 25 |
const episodeNumber = Number(episode.replace(/^episode_/, ""));
|
| 26 |
return (
|
| 27 |
<Suspense fallback={null}>
|
| 28 |
+
<EpisodeViewer org={org} dataset={dataset} episodeId={episodeNumber} />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
</Suspense>
|
| 30 |
);
|
| 31 |
}
|
src/components/data-recharts.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { useEffect, useState } from "react";
|
| 4 |
import { useTime } from "../context/time-context";
|
| 5 |
import {
|
| 6 |
LineChart,
|
|
@@ -20,8 +20,6 @@ type DataGraphProps = {
|
|
| 20 |
onChartsReady?: () => void;
|
| 21 |
};
|
| 22 |
|
| 23 |
-
import React, { useMemo } from "react";
|
| 24 |
-
|
| 25 |
const SERIES_NAME_DELIMITER = " | ";
|
| 26 |
|
| 27 |
const CHART_COLORS = [
|
|
@@ -158,42 +156,45 @@ const SingleDataGraph = React.memo(
|
|
| 158 |
tall?: boolean;
|
| 159 |
}) => {
|
| 160 |
const { currentTime, setCurrentTime } = useTime();
|
| 161 |
-
|
| 162 |
-
row: Record<string, number | Record<string, number>>,
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
}
|
| 174 |
-
} else if (
|
| 175 |
-
value !== null &&
|
| 176 |
-
typeof value === "object" &&
|
| 177 |
-
!Array.isArray(value)
|
| 178 |
-
) {
|
| 179 |
-
// If it's an object, recurse
|
| 180 |
-
Object.assign(
|
| 181 |
-
result,
|
| 182 |
-
flattenRow(
|
| 183 |
-
value,
|
| 184 |
-
prefix ? `${prefix}${SERIES_NAME_DELIMITER}${key}` : key,
|
| 185 |
-
),
|
| 186 |
-
);
|
| 187 |
}
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
| 194 |
|
| 195 |
// Flatten all rows for recharts
|
| 196 |
-
const chartData = useMemo(
|
|
|
|
|
|
|
|
|
|
| 197 |
const [dataKeys, setDataKeys] = useState<string[]>([]);
|
| 198 |
const [visibleKeys, setVisibleKeys] = useState<string[]>([]);
|
| 199 |
|
|
@@ -205,25 +206,27 @@ const SingleDataGraph = React.memo(
|
|
| 205 |
setVisibleKeys(keys);
|
| 206 |
}, [chartData]);
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
| 227 |
|
| 228 |
// Find the closest data point to the current time for highlighting
|
| 229 |
const findClosestDataIndex = (time: number) => {
|
|
@@ -254,26 +257,6 @@ const SingleDataGraph = React.memo(
|
|
| 254 |
);
|
| 255 |
const currentData = chartData[closestIndex] || {};
|
| 256 |
|
| 257 |
-
// Parse dataKeys into groups (dot notation)
|
| 258 |
-
const groups: Record<string, string[]> = {};
|
| 259 |
-
const singles: string[] = [];
|
| 260 |
-
dataKeys.forEach((key) => {
|
| 261 |
-
const parts = key.split(SERIES_NAME_DELIMITER);
|
| 262 |
-
if (parts.length > 1) {
|
| 263 |
-
const group = parts[0];
|
| 264 |
-
if (!groups[group]) groups[group] = [];
|
| 265 |
-
groups[group].push(key);
|
| 266 |
-
} else {
|
| 267 |
-
singles.push(key);
|
| 268 |
-
}
|
| 269 |
-
});
|
| 270 |
-
|
| 271 |
-
const allGroups = [...Object.keys(groups), ...singles];
|
| 272 |
-
const groupColorMap: Record<string, string> = {};
|
| 273 |
-
allGroups.forEach((group, idx) => {
|
| 274 |
-
groupColorMap[group] = CHART_COLORS[idx % CHART_COLORS.length];
|
| 275 |
-
});
|
| 276 |
-
|
| 277 |
const isGroupChecked = (group: string) =>
|
| 278 |
groups[group].every((k) => visibleKeys.includes(k));
|
| 279 |
const isGroupIndeterminate = (group: string) =>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
| 4 |
import { useTime } from "../context/time-context";
|
| 5 |
import {
|
| 6 |
LineChart,
|
|
|
|
| 20 |
onChartsReady?: () => void;
|
| 21 |
};
|
| 22 |
|
|
|
|
|
|
|
| 23 |
const SERIES_NAME_DELIMITER = " | ";
|
| 24 |
|
| 25 |
const CHART_COLORS = [
|
|
|
|
| 156 |
tall?: boolean;
|
| 157 |
}) => {
|
| 158 |
const { currentTime, setCurrentTime } = useTime();
|
| 159 |
+
const flattenRow = useCallback(
|
| 160 |
+
(row: Record<string, number | Record<string, number>>, prefix = "") => {
|
| 161 |
+
const result: Record<string, number> = {};
|
| 162 |
+
for (const [key, value] of Object.entries(row)) {
|
| 163 |
+
// Special case: if this is a group value that is a primitive, assign to prefix.key
|
| 164 |
+
if (typeof value === "number") {
|
| 165 |
+
if (prefix) {
|
| 166 |
+
result[`${prefix}${SERIES_NAME_DELIMITER}${key}`] = value;
|
| 167 |
+
} else {
|
| 168 |
+
result[key] = value;
|
| 169 |
+
}
|
| 170 |
+
} else if (
|
| 171 |
+
value !== null &&
|
| 172 |
+
typeof value === "object" &&
|
| 173 |
+
!Array.isArray(value)
|
| 174 |
+
) {
|
| 175 |
+
// If it's an object, recurse
|
| 176 |
+
Object.assign(
|
| 177 |
+
result,
|
| 178 |
+
flattenRow(
|
| 179 |
+
value,
|
| 180 |
+
prefix ? `${prefix}${SERIES_NAME_DELIMITER}${key}` : key,
|
| 181 |
+
),
|
| 182 |
+
);
|
| 183 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
}
|
| 185 |
+
if ("timestamp" in row && typeof row["timestamp"] === "number") {
|
| 186 |
+
result["timestamp"] = row["timestamp"];
|
| 187 |
+
}
|
| 188 |
+
return result;
|
| 189 |
+
},
|
| 190 |
+
[],
|
| 191 |
+
);
|
| 192 |
|
| 193 |
// Flatten all rows for recharts
|
| 194 |
+
const chartData = useMemo(
|
| 195 |
+
() => data.map((row) => flattenRow(row)),
|
| 196 |
+
[data, flattenRow],
|
| 197 |
+
);
|
| 198 |
const [dataKeys, setDataKeys] = useState<string[]>([]);
|
| 199 |
const [visibleKeys, setVisibleKeys] = useState<string[]>([]);
|
| 200 |
|
|
|
|
| 206 |
setVisibleKeys(keys);
|
| 207 |
}, [chartData]);
|
| 208 |
|
| 209 |
+
const { groups, singles, groupColorMap } = useMemo(() => {
|
| 210 |
+
const grouped: Record<string, string[]> = {};
|
| 211 |
+
const singleList: string[] = [];
|
| 212 |
+
dataKeys.forEach((key) => {
|
| 213 |
+
const parts = key.split(SERIES_NAME_DELIMITER);
|
| 214 |
+
if (parts.length > 1) {
|
| 215 |
+
const group = parts[0];
|
| 216 |
+
if (!grouped[group]) grouped[group] = [];
|
| 217 |
+
grouped[group].push(key);
|
| 218 |
+
} else {
|
| 219 |
+
singleList.push(key);
|
| 220 |
+
}
|
| 221 |
+
});
|
| 222 |
|
| 223 |
+
const allGroups = [...Object.keys(grouped), ...singleList];
|
| 224 |
+
const colorMap: Record<string, string> = {};
|
| 225 |
+
allGroups.forEach((group, idx) => {
|
| 226 |
+
colorMap[group] = CHART_COLORS[idx % CHART_COLORS.length];
|
| 227 |
+
});
|
| 228 |
+
return { groups: grouped, singles: singleList, groupColorMap: colorMap };
|
| 229 |
+
}, [dataKeys]);
|
| 230 |
|
| 231 |
// Find the closest data point to the current time for highlighting
|
| 232 |
const findClosestDataIndex = (time: number) => {
|
|
|
|
| 257 |
);
|
| 258 |
const currentData = chartData[closestIndex] || {};
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
const isGroupChecked = (group: string) =>
|
| 261 |
groups[group].every((k) => visibleKeys.includes(k));
|
| 262 |
const isGroupIndeterminate = (group: string) =>
|
src/components/urdf-viewer.tsx
CHANGED
|
@@ -542,7 +542,10 @@ export default function URDFViewer({
|
|
| 542 |
|
| 543 |
const [selectedGroup, setSelectedGroup] = useState(defaultGroup);
|
| 544 |
useEffect(() => setSelectedGroup(defaultGroup), [defaultGroup]);
|
| 545 |
-
const selectedColumns =
|
|
|
|
|
|
|
|
|
|
| 546 |
|
| 547 |
// Joint mapping
|
| 548 |
const autoMapping = useMemo(
|
|
@@ -640,7 +643,7 @@ export default function URDFViewer({
|
|
| 640 |
}
|
| 641 |
}
|
| 642 |
return values;
|
| 643 |
-
}, [chartData, frame, mapping, totalFrames, urdfJointNames]);
|
| 644 |
|
| 645 |
const currentTime = totalFrames > 0 ? (frame / fps).toFixed(2) : "0.00";
|
| 646 |
const totalTime = (totalFrames / fps).toFixed(2);
|
|
|
|
| 542 |
|
| 543 |
const [selectedGroup, setSelectedGroup] = useState(defaultGroup);
|
| 544 |
useEffect(() => setSelectedGroup(defaultGroup), [defaultGroup]);
|
| 545 |
+
const selectedColumns = useMemo(
|
| 546 |
+
() => columnGroups[selectedGroup] ?? [],
|
| 547 |
+
[columnGroups, selectedGroup],
|
| 548 |
+
);
|
| 549 |
|
| 550 |
// Joint mapping
|
| 551 |
const autoMapping = useMemo(
|
|
|
|
| 643 |
}
|
| 644 |
}
|
| 645 |
return values;
|
| 646 |
+
}, [chartData, frame, gripperRanges, mapping, totalFrames, urdfJointNames]);
|
| 647 |
|
| 648 |
const currentTime = totalFrames > 0 ? (frame / fps).toFixed(2) : "0.00";
|
| 649 |
const totalTime = (totalFrames / fps).toFixed(2);
|