File size: 2,426 Bytes
50fe3c9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import { cn } from "@/lib/utils";
import type { PreviewTab, Tab } from "@/modules/tabs";
import { useEffect, useRef } from "react";
import { PreviewPane, type PreviewPaneHandle } from "./PreviewPane";
type Props = {
tabs: Tab[];
activeId: number;
onUrlChange: (id: number, url: string) => void;
registerHandle: (id: number, handle: PreviewPaneHandle | null) => void;
};
export function PreviewStack({
tabs,
activeId,
onUrlChange,
registerHandle,
}: Props) {
const previews = tabs.filter((t): t is PreviewTab => t.kind === "preview");
const registerRef = useRef(registerHandle);
const urlChangeRef = useRef(onUrlChange);
useEffect(() => {
registerRef.current = registerHandle;
}, [registerHandle]);
useEffect(() => {
urlChangeRef.current = onUrlChange;
}, [onUrlChange]);
const refCallbacks = useRef(
new Map<number, (h: PreviewPaneHandle | null) => void>(),
);
const urlCallbacks = useRef(new Map<number, (url: string) => void>());
const getRefCallback = (id: number) => {
let cb = refCallbacks.current.get(id);
if (!cb) {
cb = (h: PreviewPaneHandle | null) => registerRef.current(id, h);
refCallbacks.current.set(id, cb);
}
return cb;
};
const getUrlCallback = (id: number) => {
let cb = urlCallbacks.current.get(id);
if (!cb) {
cb = (url: string) => urlChangeRef.current(id, url);
urlCallbacks.current.set(id, cb);
}
return cb;
};
useEffect(() => {
const live = new Set(previews.map((t) => t.id));
for (const id of refCallbacks.current.keys()) {
if (!live.has(id)) refCallbacks.current.delete(id);
}
for (const id of urlCallbacks.current.keys()) {
if (!live.has(id)) urlCallbacks.current.delete(id);
}
}, [previews]);
if (previews.length === 0) return null;
return (
<div className="relative h-full w-full">
{previews.map((t) => {
const visible = t.id === activeId;
return (
<div
key={t.id}
className={cn(
"absolute inset-0",
!visible && "invisible pointer-events-none",
)}
aria-hidden={!visible}
>
<PreviewPane
ref={getRefCallback(t.id)}
url={t.url}
visible={visible}
onUrlChange={getUrlCallback(t.id)}
/>
</div>
);
})}
</div>
);
}
|