Skip to content

Commit 2f19c1a

Browse files
committed
more resilient rrd renderer
1 parent 58a4d85 commit 2f19c1a

File tree

5 files changed

+269
-67
lines changed

5 files changed

+269
-67
lines changed

dist/index.umd.js

Lines changed: 9 additions & 9 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@fiftyone/relay": "portal:../../fiftyone/app/packages/relay",
2121
"@fiftyone/state": "portal:../../fiftyone/app/packages/state",
2222
"@fiftyone/utilities": "portal:../../fiftyone/app/packages/utilities",
23-
"@rerun-io/web-viewer": "^0.24.0",
23+
"@rerun-io/web-viewer-react": "^0.24.0",
2424
"lru-cache": "^11.0.1",
2525
"react": "^18.2.0",
2626
"react-dom": "^18.2.0",

src/RerunPanel.tsx

Lines changed: 177 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,193 @@
1-
import * as fos from "@fiftyone/state";
2-
import { getFieldsWithEmbeddedDocType } from "@fiftyone/utilities";
3-
import React, { useMemo } from "react";
4-
import { useRecoilValue } from "recoil";
5-
import { CustomErrorBoundary } from "./CustomErrorBoundary";
6-
import { RerunReactRenderer } from "./RerunReactRenderer";
1+
import React, { Component, ReactNode, Suspense, useRef } from "react";
2+
import { RerunViewerReact } from "./RerunReactRenderer";
73

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

12-
type RerunFieldDescriptor = {
13-
_cls: "RrdFile";
14-
filepath: string;
15-
version: string;
16-
};
17-
18-
export const RerunViewer = React.memo(() => {
19-
const currentSample = useRecoilValue(fos.modalSample);
20-
21-
const schema = useRecoilValue(
22-
fos.fieldSchema({ space: fos.State.SPACE.SAMPLE })
23-
);
24-
25-
const rerunFieldPath = useMemo(
26-
() =>
27-
getFieldsWithEmbeddedDocType(
28-
schema,
29-
RerunFileDescriptor.EMBEDDED_DOC_TYPE
30-
).at(0)?.path,
31-
[schema]
32-
);
8+
interface ErrorBoundaryState {
9+
hasError: boolean;
10+
error?: Error;
11+
}
3312

34-
const rrdParams = useMemo(() => {
35-
if (!rerunFieldPath || !currentSample.urls) {
36-
return undefined;
37-
}
13+
export class RerunErrorBoundary extends Component<
14+
{ children: ReactNode; action?: () => void },
15+
ErrorBoundaryState
16+
> {
17+
constructor(props: { children: ReactNode }) {
18+
super(props);
19+
this.state = { hasError: false };
20+
}
3821

39-
const filePathAndVersion = currentSample?.sample?.[
40-
rerunFieldPath
41-
] as unknown as RerunFieldDescriptor;
22+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
23+
return { hasError: true, error };
24+
}
4225

43-
const urlsStandardized = fos.getStandardizedUrls(currentSample.urls);
26+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
27+
console.error("Rerun viewer error:", error, errorInfo);
28+
}
4429

45-
const rrdFilePath = urlsStandardized[`${rerunFieldPath}.filepath`];
30+
resetError = () => {
31+
this.setState({ hasError: false, error: undefined });
32+
};
4633

47-
const url = fos.getSampleSrc(rrdFilePath);
48-
return {
49-
url,
50-
version: filePathAndVersion?.version,
51-
};
52-
}, [currentSample, rerunFieldPath]);
34+
render() {
35+
if (this.state.hasError) {
36+
return (
37+
<div
38+
style={{
39+
display: "flex",
40+
flexDirection: "column",
41+
alignItems: "center",
42+
justifyContent: "center",
43+
minHeight: "100vh",
44+
padding: "40px 20px",
45+
}}
46+
>
47+
<div
48+
style={{
49+
maxWidth: "500px",
50+
width: "100%",
51+
padding: "32px 24px",
52+
textAlign: "center",
53+
color: "#d32f2f",
54+
fontSize: "14px",
55+
border: "2px solid #d32f2f",
56+
borderRadius: "12px",
57+
backgroundColor: "#ffebee",
58+
boxShadow: "0 4px 12px rgba(211, 47, 47, 0.15)",
59+
backdropFilter: "blur(10px)",
60+
position: "relative",
61+
overflow: "hidden",
62+
}}
63+
>
64+
<div
65+
style={{
66+
position: "absolute",
67+
top: 0,
68+
left: 0,
69+
right: 0,
70+
height: "4px",
71+
backgroundColor: "#d32f2f",
72+
}}
73+
/>
74+
<div
75+
style={{
76+
fontWeight: "600",
77+
fontSize: "18px",
78+
marginBottom: "12px",
79+
color: "#b71c1c",
80+
}}
81+
>
82+
Rerun viewer error
83+
</div>
84+
<div
85+
style={{
86+
fontSize: "14px",
87+
wordBreak: "break-word",
88+
lineHeight: "1.5",
89+
marginBottom: "20px",
90+
color: "#c62828",
91+
}}
92+
>
93+
{this.state.error?.message || "An unknown error occurred"}
94+
</div>
95+
{this.props.action ? (
96+
<button
97+
onClick={() => {
98+
this.setState({ hasError: false });
99+
this.props.action();
100+
}}
101+
style={{
102+
padding: "10px 24px",
103+
backgroundColor: "#d32f2f",
104+
color: "white",
105+
border: "none",
106+
borderRadius: "8px",
107+
fontSize: "14px",
108+
fontWeight: "500",
109+
cursor: "pointer",
110+
transition: "all 0.2s ease",
111+
boxShadow: "0 2px 4px rgba(211, 47, 47, 0.2)",
112+
}}
113+
onMouseEnter={(e) => {
114+
const target = e.target as HTMLButtonElement;
115+
target.style.backgroundColor = "#b71c1c";
116+
target.style.transform = "translateY(-1px)";
117+
target.style.boxShadow = "0 4px 8px rgba(211, 47, 47, 0.3)";
118+
}}
119+
onMouseLeave={(e) => {
120+
const target = e.target as HTMLButtonElement;
121+
target.style.backgroundColor = "#d32f2f";
122+
target.style.transform = "translateY(0)";
123+
target.style.boxShadow = "0 2px 4px rgba(211, 47, 47, 0.2)";
124+
}}
125+
>
126+
Retry
127+
</button>
128+
) : (
129+
<button
130+
onClick={() => {
131+
this.setState({ hasError: false });
132+
}}
133+
style={{
134+
padding: "10px 24px",
135+
backgroundColor: "#d32f2f",
136+
color: "white",
137+
border: "none",
138+
borderRadius: "8px",
139+
fontSize: "14px",
140+
fontWeight: "500",
141+
cursor: "pointer",
142+
transition: "all 0.2s ease",
143+
boxShadow: "0 2px 4px rgba(211, 47, 47, 0.2)",
144+
}}
145+
onMouseEnter={(e) => {
146+
const target = e.target as HTMLButtonElement;
147+
target.style.backgroundColor = "#b71c1c";
148+
target.style.transform = "translateY(-1px)";
149+
target.style.boxShadow = "0 4px 8px rgba(211, 47, 47, 0.3)";
150+
}}
151+
onMouseLeave={(e) => {
152+
const target = e.target as HTMLButtonElement;
153+
target.style.backgroundColor = "#d32f2f";
154+
target.style.transform = "translateY(0)";
155+
target.style.boxShadow = "0 2px 4px rgba(211, 47, 47, 0.2)";
156+
}}
157+
>
158+
Attempt Reload
159+
</button>
160+
)}
161+
</div>
162+
</div>
163+
);
164+
}
53165

54-
if (!rrdParams) {
55-
return <div>Loading...</div>;
166+
return this.props.children;
56167
}
168+
}
169+
170+
export const RerunViewer = React.memo(() => {
171+
const errorBoundaryRef = useRef<RerunErrorBoundary>(null);
57172

58173
return (
59-
<CustomErrorBoundary>
60-
<RerunReactRenderer url={rrdParams.url} version={rrdParams.version} />
61-
</CustomErrorBoundary>
174+
<Suspense
175+
fallback={
176+
<div
177+
style={{
178+
padding: "20px",
179+
textAlign: "center",
180+
color: "#666",
181+
fontSize: "14px",
182+
}}
183+
>
184+
Loading Rerun viewer...
185+
</div>
186+
}
187+
>
188+
<RerunErrorBoundary ref={errorBoundaryRef}>
189+
<RerunViewerReact />
190+
</RerunErrorBoundary>
191+
</Suspense>
62192
);
63193
});

src/RerunReactRenderer.tsx

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,85 @@
1-
import React from "react";
1+
import * as fos from "@fiftyone/state";
2+
import { getFieldsWithEmbeddedDocType } from "@fiftyone/utilities";
23
import WebViewer from "@rerun-io/web-viewer-react";
4+
import { startTransition, useEffect, useMemo, useState } from "react";
5+
import { useRecoilValue } from "recoil";
36

4-
export const RerunReactRenderer = React.memo(
5-
({ url, version }: { url: string; version: string }) => {
6-
switch (version) {
7-
// todo: implement versioned renderers
8-
default:
9-
return <WebViewer rrd={url} width="100%" height="100%" />;
7+
export const RerunFileDescriptor = {
8+
EMBEDDED_DOC_TYPE: "fiftyone.utils.rerun.RrdFile",
9+
};
10+
11+
type RerunFieldDescriptor = {
12+
_cls: "RrdFile";
13+
filepath: string;
14+
version: string;
15+
};
16+
17+
export const RerunViewerReact = () => {
18+
const currentSample = useRecoilValue(fos.modalSample);
19+
const [stableRrdParams, setStableRrdParams] = useState<any>(null);
20+
21+
const schema = useRecoilValue(
22+
fos.fieldSchema({ space: fos.State.SPACE.SAMPLE })
23+
);
24+
25+
const rerunFieldPath = useMemo(
26+
() =>
27+
getFieldsWithEmbeddedDocType(
28+
schema,
29+
RerunFileDescriptor.EMBEDDED_DOC_TYPE
30+
).at(0)?.path,
31+
[schema]
32+
);
33+
34+
const rrdParams = useMemo(() => {
35+
if (!rerunFieldPath || !currentSample?.urls) {
36+
return undefined;
1037
}
38+
39+
try {
40+
const filePathAndVersion = currentSample?.sample?.[
41+
rerunFieldPath
42+
] as unknown as RerunFieldDescriptor;
43+
44+
const urlsStandardized = fos.getStandardizedUrls(currentSample.urls);
45+
46+
const rrdFilePath = urlsStandardized[`${rerunFieldPath}.filepath`];
47+
48+
if (!rrdFilePath) {
49+
return undefined;
50+
}
51+
52+
const url = fos.getSampleSrc(rrdFilePath);
53+
return {
54+
url,
55+
version: filePathAndVersion?.version,
56+
};
57+
} catch (error) {
58+
console.error("Error processing Rerun parameters:", error);
59+
return undefined;
60+
}
61+
}, [currentSample, rerunFieldPath]);
62+
63+
useEffect(() => {
64+
if (rrdParams) {
65+
startTransition(() => {
66+
setStableRrdParams(rrdParams);
67+
});
68+
}
69+
}, [rrdParams]);
70+
71+
if (!stableRrdParams) {
72+
return <div>Resolving URL...</div>;
1173
}
12-
);
74+
75+
return (
76+
<WebViewer
77+
rrd={stableRrdParams.url}
78+
height="100%"
79+
width="100%"
80+
onReady={() => {
81+
console.log("web viewer ready");
82+
}}
83+
/>
84+
);
85+
};

yarn.lock

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,6 @@ __metadata:
651651
"@fiftyone/relay": "portal:../fiftyone/app/packages/relay"
652652
"@fiftyone/state": "portal:../fiftyone/app/packages/state"
653653
"@fiftyone/utilities": "portal:../fiftyone/app/packages/utilities"
654-
"@rerun-io/web-viewer": "npm:^0.24.0"
655654
"@rerun-io/web-viewer-react": "npm:^0.24.0"
656655
"@rollup/plugin-node-resolve": "npm:^15.3.0"
657656
"@types/node": "npm:^22.7.6"
@@ -1102,7 +1101,7 @@ __metadata:
11021101
languageName: node
11031102
linkType: hard
11041103

1105-
"@rerun-io/web-viewer@npm:0.24.0, @rerun-io/web-viewer@npm:^0.24.0":
1104+
"@rerun-io/web-viewer@npm:0.24.0":
11061105
version: 0.24.0
11071106
resolution: "@rerun-io/web-viewer@npm:0.24.0"
11081107
checksum: 10c0/b7d4d6b8fd2fc83924fed17c316629c0c450d967ca6136eabb9e48f5585e50eb0c131d9924a460159d11a567caf04028e79dc5f87f169dbaddc6d879993ea74c

0 commit comments

Comments
 (0)