Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ node_modules

__pycache__

examples/**/data
examples/**/data
examples/nuscenes_data
27 changes: 26 additions & 1 deletion dist/index.umd.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@fiftyone/relay": "portal:../../fiftyone/app/packages/relay",
"@fiftyone/state": "portal:../../fiftyone/app/packages/state",
"@fiftyone/utilities": "portal:../../fiftyone/app/packages/utilities",
"@rerun-io/web-viewer": "^0.20.1",
"@rerun-io/web-viewer-react": "^0.24.0",
"lru-cache": "^11.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
199 changes: 150 additions & 49 deletions src/RerunPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,166 @@
import * as fos from "@fiftyone/state";
import { getFieldsWithEmbeddedDocType } from "@fiftyone/utilities";
import React, { useMemo } from "react";
import { useRecoilValue } from "recoil";
import { CustomErrorBoundary } from "./CustomErrorBoundary";
import { RrdIframeRenderer } from "./RrdIframeRenderer";
import React, { Component, ReactNode, Suspense, useRef } from "react";
import { RerunViewerReact } from "./RerunReactRenderer";

export const RerunFileDescriptor = {
EMBEDDED_DOC_TYPE: "fiftyone.utils.rerun.RrdFile",
};

type RerunFieldDescriptor = {
_cls: "RrdFile";
filepath: string;
version: string;
};

export const RerunViewer = React.memo(() => {
const currentSample = useRecoilValue(fos.modalSample);

const schema = useRecoilValue(
fos.fieldSchema({ space: fos.State.SPACE.SAMPLE })
);

const rerunFieldPath = useMemo(
() =>
getFieldsWithEmbeddedDocType(
schema,
RerunFileDescriptor.EMBEDDED_DOC_TYPE
).at(0)?.path,
[schema]
);
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}

const rrdParams = useMemo(() => {
if (!rerunFieldPath || !currentSample.urls) {
return undefined;
}
export class RerunErrorBoundary extends Component<
{ children: ReactNode; action?: () => void },
ErrorBoundaryState
> {
constructor(props: { children: ReactNode }) {
super(props);
this.state = { hasError: false };
}

const filePathAndVersion = currentSample?.sample?.[
rerunFieldPath
] as unknown as RerunFieldDescriptor;
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}

const urlsStandardized = fos.getStandardizedUrls(currentSample.urls);
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Rerun viewer error:", error, errorInfo);
}

const rrdFilePath = urlsStandardized[`${rerunFieldPath}.filepath`];
resetError = () => {
this.setState({ hasError: false, error: undefined });
};

const url = fos.getSampleSrc(rrdFilePath);
return {
url,
version: filePathAndVersion?.version,
};
}, [currentSample, rerunFieldPath]);
render() {
if (this.state.hasError) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
padding: "40px 20px",
}}
>
<div
style={{
maxWidth: "500px",
width: "100%",
padding: "32px 24px",
textAlign: "center",
color: "#d32f2f",
fontSize: "14px",
border: "1px solid #d32f2f",
borderRadius: "12px",
backgroundColor: "#ffebee",
backdropFilter: "blur(10px)",
position: "relative",
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: "4px",
backgroundColor: "#d32f2f",
}}
/>
<div
style={{
fontWeight: "600",
fontSize: "18px",
marginBottom: "12px",
color: "#b71c1c",
}}
>
Rerun viewer error
</div>
<div
style={{
fontSize: "14px",
wordBreak: "break-word",
lineHeight: "1.5",
marginBottom: "20px",
color: "#c62828",
}}
>
{this.state.error?.message || "An unknown error occurred"}
</div>
{this.props.action ? (
<button
onClick={() => {
this.setState({ hasError: false });
this.props.action();
}}
style={{
padding: "10px 24px",
backgroundColor: "#d32f2f",
color: "white",
border: "none",
borderRadius: "8px",
fontSize: "14px",
fontWeight: "500",
cursor: "pointer",
transition: "all 0.2s ease",
}}
>
Retry
</button>
) : (
<button
onClick={() => {
this.setState({ hasError: false });
}}
style={{
padding: "10px 24px",
backgroundColor: "#d32f2f",
color: "white",
border: "none",
borderRadius: "8px",
fontSize: "14px",
fontWeight: "500",
cursor: "pointer",
transition: "all 0.2s ease",
}}
>
Attempt Reload
</button>
)}
</div>
</div>
);
}

if (!rrdParams) {
return <div>Loading...</div>;
return this.props.children;
}
}

export const RerunViewer = React.memo(() => {
const errorBoundaryRef = useRef<RerunErrorBoundary>(null);

return (
<CustomErrorBoundary>
{/* use iframe until versioned web viewer renderer is more stable, note: vite will not bundle any rerun deps */}
{/* <RrdWebViewerRenderer url={rrdParams.url} version={rrdParams.version} /> */}
<RrdIframeRenderer url={rrdParams.url} />
</CustomErrorBoundary>
<Suspense
fallback={
<div
style={{
padding: "20px",
textAlign: "center",
color: "#666",
fontSize: "14px",
}}
>
Loading Rerun viewer...
</div>
}
>
<RerunErrorBoundary ref={errorBoundaryRef}>
<RerunViewerReact />
</RerunErrorBoundary>
</Suspense>
);
});
85 changes: 85 additions & 0 deletions src/RerunReactRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as fos from "@fiftyone/state";
import { getFieldsWithEmbeddedDocType } from "@fiftyone/utilities";
import WebViewer from "@rerun-io/web-viewer-react";
import { startTransition, useEffect, useMemo, useState } from "react";
import { useRecoilValue } from "recoil";

export const RerunFileDescriptor = {
EMBEDDED_DOC_TYPE: "fiftyone.utils.rerun.RrdFile",
};

type RerunFieldDescriptor = {
_cls: "RrdFile";
filepath: string;
version: string;
};

export const RerunViewerReact = () => {
const currentSample = useRecoilValue(fos.modalSample);
const [stableRrdParams, setStableRrdParams] = useState<any>(null);

const schema = useRecoilValue(
fos.fieldSchema({ space: fos.State.SPACE.SAMPLE })
);

const rerunFieldPath = useMemo(
() =>
getFieldsWithEmbeddedDocType(
schema,
RerunFileDescriptor.EMBEDDED_DOC_TYPE
).at(0)?.path,
[schema]
);

const rrdParams = useMemo(() => {
if (!rerunFieldPath || !currentSample?.urls) {
return undefined;
}

try {
const filePathAndVersion = currentSample?.sample?.[
rerunFieldPath
] as unknown as RerunFieldDescriptor;

const urlsStandardized = fos.getStandardizedUrls(currentSample.urls);

const rrdFilePath = urlsStandardized[`${rerunFieldPath}.filepath`];

if (!rrdFilePath) {
return undefined;
}

const url = fos.getSampleSrc(rrdFilePath);
return {
url,
version: filePathAndVersion?.version,
};
} catch (error) {
console.error("Error processing Rerun parameters:", error);
return undefined;
}
}, [currentSample, rerunFieldPath]);

useEffect(() => {
if (rrdParams) {
startTransition(() => {
setStableRrdParams(rrdParams);
});
}
}, [rrdParams]);

if (!stableRrdParams) {
return <div>Resolving URL...</div>;
}

return (
<WebViewer
rrd={stableRrdParams.url}
height="100%"
width="100%"
onReady={() => {
console.log("web viewer ready");
}}
/>
);
};
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ fop.registerComponent({
panelOptions: {
surfaces: "modal",
helpMarkdown: `Rereun viewer for FiftyOne`,
isNew: false,
},
});
Loading