From b56f328a05a46280587cb2aecf4d2eef98bbb46c Mon Sep 17 00:00:00 2001 From: nickbar01234 Date: Fri, 10 Oct 2025 00:08:42 -0400 Subject: [PATCH] Mount monaco outside --- .../src/components/dialog/LeaveRoomDialog.tsx | 7 +- .../components/panel/editor/tab/CodeTab.tsx | 52 ++++++++- .../panel/info/LeetCodeQuestions.tsx | 8 +- .../src/components/panel/problem/index.tsx | 23 ++-- .../portals/CodeBuddyMonacoPortal.tsx | 13 +++ .../IFramePortal.tsx} | 6 +- extension/src/entrypoints/content.tsx | 6 +- extension/src/hooks/store/index.ts | 12 +- extension/src/store/htmlStore.ts | 110 ++++++++---------- 9 files changed, 146 insertions(+), 91 deletions(-) create mode 100644 extension/src/components/portals/CodeBuddyMonacoPortal.tsx rename extension/src/components/{iframe/IframeContainer.tsx => portals/IFramePortal.tsx} (61%) diff --git a/extension/src/components/dialog/LeaveRoomDialog.tsx b/extension/src/components/dialog/LeaveRoomDialog.tsx index 08dbca42..9ad4aed3 100644 --- a/extension/src/components/dialog/LeaveRoomDialog.tsx +++ b/extension/src/components/dialog/LeaveRoomDialog.tsx @@ -1,4 +1,7 @@ -import { useHtmlActions, useRoomActions } from "@cb/hooks/store"; +import { + useLeetCodeProblemsHtmlActions, + useRoomActions, +} from "@cb/hooks/store"; import { Button } from "@cb/lib/components/ui/button"; import { DialogClose } from "@cb/lib/components/ui/dialog"; import { DialogOverlay } from "@radix-ui/react-dialog"; @@ -9,7 +12,7 @@ import { RoomDialog, baseButtonClassName } from "./RoomDialog"; export function LeaveRoomDialog() { const { leave, closeSidebarTab } = useRoomActions(); - const { blurHtml, unblurHtml } = useHtmlActions(); + const { blurHtml, unblurHtml } = useLeetCodeProblemsHtmlActions(); const leaveRoomThrottled = React.useMemo(() => { return throttle((event: React.MouseEvent) => { diff --git a/extension/src/components/panel/editor/tab/CodeTab.tsx b/extension/src/components/panel/editor/tab/CodeTab.tsx index 5314ad98..cc854760 100644 --- a/extension/src/components/panel/editor/tab/CodeTab.tsx +++ b/extension/src/components/panel/editor/tab/CodeTab.tsx @@ -1,14 +1,56 @@ import { SkeletonWrapper } from "@cb/components/ui/SkeletonWrapper"; -import { DOM } from "@cb/constants"; +import { useCodeBuddyMonacoHtmlActions } from "@cb/hooks/store"; import React from "react"; +// note: This needs to match the sidepanel icons +const MONACO_EDITOR_Z_INDEX = 1000; + export const CodeTab: React.FC = () => { + const { showHtml } = useCodeBuddyMonacoHtmlActions(); + + const onContainerRefCallback = React.useCallback( + (node: HTMLElement | null) => { + if (!node) return; + + let lastRect: DOMRect; + let animationFrameId: number; + const repositionIframeOnPositionChange = () => { + const rect = node.getBoundingClientRect(); + if ( + !lastRect || + rect.top !== lastRect.top || + rect.left !== lastRect.left + ) { + showHtml(node, MONACO_EDITOR_Z_INDEX); + lastRect = rect; + } + animationFrameId = requestAnimationFrame( + repositionIframeOnPositionChange + ); + }; + + animationFrameId = requestAnimationFrame( + repositionIframeOnPositionChange + ); + + const repositionIframeOnWindowResize = () => { + lastRect = node.getBoundingClientRect(); + showHtml(node, MONACO_EDITOR_Z_INDEX); + }; + + window.addEventListener("resize", repositionIframeOnPositionChange); + + return () => { + cancelAnimationFrame(animationFrameId); + window.removeEventListener("resize", repositionIframeOnWindowResize); + }; + }, + [showHtml] + ); + return ( -
+
); }; diff --git a/extension/src/components/panel/info/LeetCodeQuestions.tsx b/extension/src/components/panel/info/LeetCodeQuestions.tsx index 0de52d1d..2e6fade7 100644 --- a/extension/src/components/panel/info/LeetCodeQuestions.tsx +++ b/extension/src/components/panel/info/LeetCodeQuestions.tsx @@ -1,5 +1,9 @@ import { QuestionSelectorPanel } from "@cb/components/panel/problem"; -import { useHtmlActions, useRoomActions, useRoomData } from "@cb/hooks/store"; +import { + useLeetCodeProblemsHtmlActions, + useRoomActions, + useRoomData, +} from "@cb/hooks/store"; import { SidebarTabIdentifier } from "@cb/store"; import { DialogTitle } from "@radix-ui/react-dialog"; import React from "react"; @@ -8,7 +12,7 @@ import { SidebarTabHeader, SidebarTabLayout } from "./SidebarTabLayout"; export const LeetCodeQuestions = () => { const { activeSidebarTab, questions } = useRoomData(); const { addQuestion, closeSidebarTab } = useRoomActions(); - const { hideHtml } = useHtmlActions(); + const { hideHtml } = useLeetCodeProblemsHtmlActions(); React.useEffect(() => { if (activeSidebarTab === undefined) hideHtml(); diff --git a/extension/src/components/panel/problem/index.tsx b/extension/src/components/panel/problem/index.tsx index 673bf466..8a3ff27b 100644 --- a/extension/src/components/panel/problem/index.tsx +++ b/extension/src/components/panel/problem/index.tsx @@ -1,6 +1,6 @@ import { SkeletonWrapper } from "@cb/components/ui/SkeletonWrapper"; import { DOM, EXTENSION } from "@cb/constants"; -import { useHtmlActions } from "@cb/hooks/store"; +import { useLeetCodeProblemsHtmlActions } from "@cb/hooks/store"; import useResource from "@cb/hooks/useResource"; import { Question } from "@cb/types"; import React, { useEffect } from "react"; @@ -8,6 +8,8 @@ import { toast } from "sonner"; const INJECTED_ATTRIBUTE = "data-injected"; +const LEETCODE_PROBLEM_Z_INDEX = 3000; + interface QuestionSelectorPanelProps { handleQuestionSelect: (link: string) => void; filterQuestions: Question[]; @@ -19,7 +21,7 @@ export const QuestionSelectorPanel = React.memo( const { register: registerObserver } = useResource({ name: "observer", }); - const iframeActions = useHtmlActions(); + const iframeActions = useLeetCodeProblemsHtmlActions(); const onContainerRefCallback = React.useCallback( (node: HTMLElement | null) => { @@ -34,7 +36,7 @@ export const QuestionSelectorPanel = React.memo( rect.top !== lastRect.top || rect.left !== lastRect.left ) { - iframeActions.showHtml(node); + iframeActions.showHtml(node, LEETCODE_PROBLEM_Z_INDEX); lastRect = rect; } animationFrameId = requestAnimationFrame( @@ -48,7 +50,7 @@ export const QuestionSelectorPanel = React.memo( const repositionIframeOnWindowResize = () => { lastRect = node.getBoundingClientRect(); - iframeActions.showHtml(node); + iframeActions.showHtml(node, LEETCODE_PROBLEM_Z_INDEX); }; window.addEventListener("resize", repositionIframeOnPositionChange); @@ -64,10 +66,6 @@ export const QuestionSelectorPanel = React.memo( useEffect(() => { setLoading(true); - if (iframeActions.isContentProcessed()) { - setLoading(false); - } - const iframe = iframeActions.getHtmlElement(); if (iframe) { const processIframe = async () => { @@ -146,16 +144,13 @@ export const QuestionSelectorPanel = React.memo( obs.disconnect() ); observer.observe(rowContainer, { childList: true }); - processQuestionLinks(); - - iframeActions.setContentProcessed(true); + await processQuestionLinks(); setLoading(false); } catch (e) { console.error("Unable to mount Leetcode iframe", e); toast.error( "Unable to show question selector, please try again later." ); - setLoading(false); } }; @@ -171,7 +166,7 @@ export const QuestionSelectorPanel = React.memo( } }; - if (iframeActions.isHtmlLoaded()) { + if (iframe.contentDocument?.readyState === "complete") { await processIframeDocument(); } else { // Set up load event listener @@ -186,8 +181,6 @@ export const QuestionSelectorPanel = React.memo( processIframe(); } - - return () => iframeActions.setContentProcessed(false); }, [ handleQuestionSelect, filterQuestions, diff --git a/extension/src/components/portals/CodeBuddyMonacoPortal.tsx b/extension/src/components/portals/CodeBuddyMonacoPortal.tsx new file mode 100644 index 00000000..d3e0ed50 --- /dev/null +++ b/extension/src/components/portals/CodeBuddyMonacoPortal.tsx @@ -0,0 +1,13 @@ +import { DOM } from "@cb/constants"; +import { useCodeBuddyMonacoHtmlActions } from "@cb/hooks/store"; + +export const CodeBuddyMonacoPortal = () => { + const { setHtmlElement } = useCodeBuddyMonacoHtmlActions(); + return ( +
setHtmlElement(node)} + className="hidden" + /> + ); +}; diff --git a/extension/src/components/iframe/IframeContainer.tsx b/extension/src/components/portals/IFramePortal.tsx similarity index 61% rename from extension/src/components/iframe/IframeContainer.tsx rename to extension/src/components/portals/IFramePortal.tsx index 5667fa2e..be0116a3 100644 --- a/extension/src/components/iframe/IframeContainer.tsx +++ b/extension/src/components/portals/IFramePortal.tsx @@ -1,9 +1,9 @@ import { URLS } from "@cb/constants"; -import { useHtmlActions } from "@cb/hooks/store"; +import { useLeetCodeProblemsHtmlActions } from "@cb/hooks/store"; import React from "react"; -export const IframeContainer: React.FC = () => { - const { setHtmlElement } = useHtmlActions(); +export const IFramePortal: React.FC = () => { + const { setHtmlElement } = useLeetCodeProblemsHtmlActions(); return (