Skip to content

Commit b040052

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

File tree

5 files changed

+242
-67
lines changed

5 files changed

+242
-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: 150 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,166 @@
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: "1px solid #d32f2f",
56+
borderRadius: "12px",
57+
backgroundColor: "#ffebee",
58+
backdropFilter: "blur(10px)",
59+
position: "relative",
60+
overflow: "hidden",
61+
}}
62+
>
63+
<div
64+
style={{
65+
position: "absolute",
66+
top: 0,
67+
left: 0,
68+
right: 0,
69+
height: "4px",
70+
backgroundColor: "#d32f2f",
71+
}}
72+
/>
73+
<div
74+
style={{
75+
fontWeight: "600",
76+
fontSize: "18px",
77+
marginBottom: "12px",
78+
color: "#b71c1c",
79+
}}
80+
>
81+
Rerun viewer error
82+
</div>
83+
<div
84+
style={{
85+
fontSize: "14px",
86+
wordBreak: "break-word",
87+
lineHeight: "1.5",
88+
marginBottom: "20px",
89+
color: "#c62828",
90+
}}
91+
>
92+
{this.state.error?.message || "An unknown error occurred"}
93+
</div>
94+
{this.props.action ? (
95+
<button
96+
onClick={() => {
97+
this.setState({ hasError: false });
98+
this.props.action();
99+
}}
100+
style={{
101+
padding: "10px 24px",
102+
backgroundColor: "#d32f2f",
103+
color: "white",
104+
border: "none",
105+
borderRadius: "8px",
106+
fontSize: "14px",
107+
fontWeight: "500",
108+
cursor: "pointer",
109+
transition: "all 0.2s ease",
110+
}}
111+
>
112+
Retry
113+
</button>
114+
) : (
115+
<button
116+
onClick={() => {
117+
this.setState({ hasError: false });
118+
}}
119+
style={{
120+
padding: "10px 24px",
121+
backgroundColor: "#d32f2f",
122+
color: "white",
123+
border: "none",
124+
borderRadius: "8px",
125+
fontSize: "14px",
126+
fontWeight: "500",
127+
cursor: "pointer",
128+
transition: "all 0.2s ease",
129+
}}
130+
>
131+
Attempt Reload
132+
</button>
133+
)}
134+
</div>
135+
</div>
136+
);
137+
}
53138

54-
if (!rrdParams) {
55-
return <div>Loading...</div>;
139+
return this.props.children;
56140
}
141+
}
142+
143+
export const RerunViewer = React.memo(() => {
144+
const errorBoundaryRef = useRef<RerunErrorBoundary>(null);
57145

58146
return (
59-
<CustomErrorBoundary>
60-
<RerunReactRenderer url={rrdParams.url} version={rrdParams.version} />
61-
</CustomErrorBoundary>
147+
<Suspense
148+
fallback={
149+
<div
150+
style={{
151+
padding: "20px",
152+
textAlign: "center",
153+
color: "#666",
154+
fontSize: "14px",
155+
}}
156+
>
157+
Loading Rerun viewer...
158+
</div>
159+
}
160+
>
161+
<RerunErrorBoundary ref={errorBoundaryRef}>
162+
<RerunViewerReact />
163+
</RerunErrorBoundary>
164+
</Suspense>
62165
);
63166
});

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)