Skip to content

Commit efb97e7

Browse files
committed
update some comments and start test of an immortal html component with key to solve some subtle problems with the jupyter html plugin architecture, windowing, splitting frames, etc.
1 parent b175112 commit efb97e7

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

src/packages/frontend/editors/slate/elements/codemirror.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export const SlateCodeMirror: React.FC<Props> = React.memo(
9494
} else {
9595
// everything selected, so now select all editor content.
9696
// NOTE that this only makes sense if we change focus
97-
// to the enclosing select editor, thus loosing the
97+
// to the enclosing select editor, thus losing the
9898
// cm editor focus, which is a bit weird.
9999
ReactEditor.focus(editor);
100100
selectAll(editor);

src/packages/frontend/jupyter/output-messages/cached-iframe.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
/*
22
3-
It is completely impossible in modern times to move an iframe in the DOM without loosing state,
4-
as explained here:
5-
https://stackoverflow.com/questions/8318264/how-to-move-an-iframe-in-the-dom-without-losing-its-state
3+
It is completely impossible in modern times to move an iframe's position in the DOM *tree*
4+
without losing state, as explained here:
65
7-
An annoying issue is that if you do shift+tab to get docs or hit the TimeTravel button or anything to
8-
split the Jupyter frame, then the iframes of course get reset. That was a problem before this though,
9-
i.e., it's not special to using windowing.
6+
https://stackoverflow.com/questions/8318264/how-to-move-an-iframe-in-the-dom-without-losing-its-state
7+
8+
That's what we instead use position absolute, and literally move the iframe's position on the screen.
9+
We never move it in the dom.
10+
11+
Another unsolved issue is that if you do shift+tab to get docs or hit the TimeTravel button
12+
or anything to split the Jupyter frame, then the iframes of course get reset. That was
13+
a problem before this though, i.e., it's not special to using windowing. However, it
14+
is a problem we intend to solve, e.g., maybe we put the iframes somewhere else in the
15+
DOM and can reuse when rendering the notebook after the frame split happens.
1016
*/
1117

1218
import { Button, Tooltip } from "antd";
@@ -18,7 +24,8 @@ import { delay } from "awaiting";
1824
import useIsMountedRef from "@cocalc/frontend/app-framework/is-mounted-hook";
1925
import useResizeObserver from "use-resize-observer";
2026

21-
// This is just an initial default height; the actual height of the iframe resizes to the content.
27+
// This is just an initial default height; the actual height of the iframe should
28+
// resize to the content.
2229
const HEIGHT = "70vh";
2330

2431
interface Props {

src/packages/frontend/jupyter/output-messages/iframe.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ interface Props {
2929
}
3030

3131
export default function IFrame(props: Props) {
32-
const iframeContext = useIFrameContext(); // we only use cached iframe if the iframecontext is setup, e.g., it is in Jupyter notebooks, but not in whiteboards.
32+
// we only use cached iframe if the iframecontext is setup, e.g., it is in Jupyter notebooks, but not in whiteboards.
33+
const iframeContext = useIFrameContext();
3334
return iframeContext.iframeDivRef == null || props.cacheId == null ? (
3435
<NonCachedIFrame {...props} />
3536
) : (
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { useCallback, useEffect, useRef } from "react";
2+
import $ from "jquery";
3+
4+
// This is just an initial default height; the actual height of the iframe should
5+
// resize to the content.
6+
const HEIGHT = "70vh";
7+
8+
interface Props {
9+
key: string;
10+
html: string;
11+
}
12+
13+
const immortals: { [key: string]: any } = {};
14+
15+
export default function Immortal({ key, html }: Props) {
16+
const divRef = useRef<any>(null);
17+
const eltRef = useRef<any>(null);
18+
const intervalRef = useRef<any>(null);
19+
20+
const position = useCallback(() => {
21+
// make it so eltRef.current is exactly positioned on top of divRef.current using CSS
22+
if (eltRef.current == null || divRef.current == null) {
23+
return;
24+
}
25+
const eltRect = eltRef.current.getBoundingClientRect();
26+
const divRect = divRef.current.getBoundingClientRect();
27+
let deltaTop = divRect.top - eltRect.top;
28+
if (deltaTop) {
29+
if (eltRef.current.style.top) {
30+
deltaTop += parseFloat(eltRef.current.style.top.slice(0, -2));
31+
}
32+
eltRef.current.style.top = `${deltaTop}px`;
33+
}
34+
let deltaLeft = divRect.left - eltRect.left;
35+
if (deltaLeft) {
36+
if (eltRef.current.style.left) {
37+
deltaLeft += parseFloat(eltRef.current.style.left.slice(0, -2));
38+
}
39+
eltRef.current.style.left = `${deltaLeft}px`;
40+
}
41+
}, []);
42+
43+
useEffect(() => {
44+
if (divRef.current == null) {
45+
return;
46+
}
47+
let elt;
48+
if (immortals[key] == null) {
49+
elt = immortals[key] = $(
50+
`<div style="border:0;overflow:hidden;width:100%;height:${HEIGHT};position:absolute;left:130px"/>${html}</div>`,
51+
);
52+
$("body").append(elt);
53+
} else {
54+
elt = immortals[key];
55+
elt.show();
56+
}
57+
eltRef.current = elt[0];
58+
intervalRef.current = setInterval(position, 500);
59+
60+
return () => {
61+
// unmounting so hide
62+
elt.hide();
63+
if (intervalRef.current) {
64+
clearInterval(intervalRef.current);
65+
}
66+
};
67+
}, []);
68+
69+
return <div ref={divRef} />;
70+
}

0 commit comments

Comments
 (0)