70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
import {
|
|
useContext,
|
|
useEffect,
|
|
useLayoutEffect,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import Frame from "./Frame";
|
|
import { DragContext } from "./SplitPane";
|
|
|
|
/**
|
|
* A wrapper around an iframe that loads any changes to the src in the background,
|
|
* keeping around the current content in the meantime. It does this by listening
|
|
* to the "load" event and swapping between two underlying iframe elements
|
|
*/
|
|
export function useRefreshableIframe(
|
|
src: string | undefined,
|
|
onLoad: (frame: HTMLIFrameElement) => void
|
|
) {
|
|
const isPaneDragging = useContext(DragContext);
|
|
const refs = [
|
|
useRef<HTMLIFrameElement>(null),
|
|
useRef<HTMLIFrameElement>(null),
|
|
];
|
|
const [index, setIndex] = useState(0);
|
|
|
|
const [isLoadingContent, setIsLoadingContent] = useState(false);
|
|
|
|
const [firstLoad, setFirstLoad] = useState(false);
|
|
|
|
useEffect(() => {
|
|
function onLoadEvent(this: HTMLIFrameElement) {
|
|
onLoad(this);
|
|
}
|
|
const first = refs[0].current;
|
|
const second = refs[1].current;
|
|
if (first && second) {
|
|
first.addEventListener("load", onLoadEvent);
|
|
second.addEventListener("load", onLoadEvent);
|
|
return () => {
|
|
first.removeEventListener("load", onLoadEvent);
|
|
second.removeEventListener("load", onLoadEvent);
|
|
};
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable across renders and should not trigger re-subscription
|
|
}, [onLoad]);
|
|
|
|
function listen() {
|
|
if (!firstLoad) {
|
|
setFirstLoad(true);
|
|
}
|
|
requestAnimationFrame(() => {
|
|
setIndex(index === 0 ? 1 : 0);
|
|
setIsLoadingContent(false);
|
|
});
|
|
}
|
|
function setUrl(url: string) {
|
|
setIsLoadingContent(true);
|
|
const nextIndex = index === 0 ? 1 : 0;
|
|
const nextRef = refs[nextIndex].current;
|
|
if (nextRef) {
|
|
if (isLoadingContent) {
|
|
nextRef.removeEventListener("load", listen);
|
|
}
|
|
nextRef.addEventListener("load", listen, {
|
|
once: true,
|
|
});
|
|
nextRef.src = url;
|
|
}
|
|
}
|
|
useLayoutEffect(() => {
|
|
if (src) {
|
|
setUrl(src);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- setUrl depends on mutable state that would cause infinite loops if included
|
|
}, [src]);
|
|
const isLoading = isLoadingContent;
|
|
return {
|
|
firstLoad,
|
|
isLoading,
|
|
refresh() {
|
|
if (src) {
|
|
setUrl(src);
|
|
}
|
|
},
|
|
frame: (
|
|
<>
|
|
<Frame
|
|
innerRef={refs[0]}
|
|
style={{
|
|
opacity: index === 0 ? 1 : 0,
|
|
zIndex: index === 0 ? 10 : 5,
|
|
pointerEvents: isLoading || isPaneDragging ? "none" : "auto",
|
|
}}
|
|
sandbox="allow-forms allow-modals allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox"
|
|
/>
|
|
<Frame
|
|
innerRef={refs[1]}
|
|
style={{
|
|
opacity: index === 1 ? 1 : 0,
|
|
zIndex: index === 1 ? 10 : 5,
|
|
pointerEvents: isLoading || isPaneDragging ? "none" : "auto",
|
|
}}
|
|
sandbox="allow-forms allow-modals allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox"
|
|
/>
|
|
</>
|
|
),
|
|
};
|
|
}
|