diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index a63cceb25..857783375 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -51,7 +51,7 @@ function makeViewModel(blockId: string, blockView: string, nodeModel: NodeModel) return makeCpuPlotViewModel(blockId); } if (blockView === "help") { - return makeHelpViewModel(); + return makeHelpViewModel(blockId); } return makeDefaultViewModel(blockId, blockView); } diff --git a/frontend/app/store/contextmenu.ts b/frontend/app/store/contextmenu.ts index 6b8e1c2d9..5cbcbc836 100644 --- a/frontend/app/store/contextmenu.ts +++ b/frontend/app/store/contextmenu.ts @@ -24,9 +24,16 @@ class ContextMenuModelType { role: item.role, type: item.type, label: item.label, + sublabel: item.sublabel, id: crypto.randomUUID(), checked: item.checked, }; + if (item.visible === false) { + electronItem.visible = false; + } + if (item.enabled === false) { + electronItem.enabled = false; + } if (item.click) { this.handlers.set(electronItem.id, item.click); } diff --git a/frontend/app/view/helpview/helpview.tsx b/frontend/app/view/helpview/helpview.tsx index 2032821c1..8fb2e2750 100644 --- a/frontend/app/view/helpview/helpview.tsx +++ b/frontend/app/view/helpview/helpview.tsx @@ -2,26 +2,63 @@ // SPDX-License-Identifier: Apache-2.0 import { getApi } from "@/app/store/global"; -import { useState } from "react"; +import { WebviewTag } from "electron"; +import { createRef, useEffect, useState } from "react"; import "./helpview.less"; class HelpViewModel implements ViewModel { viewType: string; + blockId: string; + webviewRef: React.RefObject; - constructor() { + constructor(blockId: string) { this.viewType = "help"; + this.blockId = blockId; + this.webviewRef = createRef(); } } -function makeHelpViewModel() { - return new HelpViewModel(); +function makeHelpViewModel(blockId: string) { + return new HelpViewModel(blockId); } -function HelpView({}: { model: HelpViewModel }) { +function HelpView({ model }: { model: HelpViewModel }) { const [url] = useState(() => getApi().getDocsiteUrl()); + const [webContentsId, setWebContentsId] = useState(null); + const [domReady, setDomReady] = useState(false); + + useEffect(() => { + if (model.webviewRef.current && domReady) { + const wcId = model.webviewRef.current.getWebContentsId?.(); + if (wcId) { + setWebContentsId(wcId); + } + } + }, [model.webviewRef.current, domReady]); + + useEffect(() => { + const webview = model.webviewRef.current; + if (!webview) { + return; + } + const handleDomReady = () => { + setDomReady(true); + }; + webview.addEventListener("dom-ready", handleDomReady); + return () => { + webview.removeEventListener("dom-ready", handleDomReady); + }; + }); + return (
- +
); } diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 6c771fae7..90a000991 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -441,69 +441,69 @@ const WebView = memo(({ model }: WebViewProps) => { useEffect(() => { const webview = model.webviewRef.current; - - if (webview) { - const navigateListener = (e: any) => { - model.handleNavigate(e.url); - }; - const newWindowHandler = (e: any) => { - e.preventDefault(); - const newUrl = e.detail.url; - console.log("webview new-window event:", newUrl); - fireAndForget(() => openLink(newUrl, true)); - }; - const startLoadingHandler = () => { - model.setRefreshIcon("xmark-large"); - model.setIsLoading(true); - webview.style.backgroundColor = "transparent"; - }; - const stopLoadingHandler = () => { - model.setRefreshIcon("rotate-right"); - model.setIsLoading(false); - setBgColor(); - }; - const failLoadHandler = (e: any) => { - if (e.errorCode === -3) { - console.warn("Suppressed ERR_ABORTED error", e); - } else { - console.error(`Failed to load ${e.validatedURL}: ${e.errorDescription}`); - } - }; - const webviewFocus = () => { - getApi().setWebviewFocus(webview.getWebContentsId()); - model.nodeModel.focusNode(); - }; - const webviewBlur = () => { - getApi().setWebviewFocus(null); - }; - const handleDomReady = () => { - setDomReady(true); - setBgColor(); - }; - - webview.addEventListener("did-navigate-in-page", navigateListener); - webview.addEventListener("did-navigate", navigateListener); - webview.addEventListener("did-start-loading", startLoadingHandler); - webview.addEventListener("did-stop-loading", stopLoadingHandler); - webview.addEventListener("new-window", newWindowHandler); - webview.addEventListener("did-fail-load", failLoadHandler); - webview.addEventListener("focus", webviewFocus); - webview.addEventListener("blur", webviewBlur); - webview.addEventListener("dom-ready", handleDomReady); - - // Clean up event listeners on component unmount - return () => { - webview.removeEventListener("did-navigate", navigateListener); - webview.removeEventListener("did-navigate-in-page", navigateListener); - webview.removeEventListener("new-window", newWindowHandler); - webview.removeEventListener("did-fail-load", failLoadHandler); - webview.removeEventListener("did-start-loading", startLoadingHandler); - webview.removeEventListener("did-stop-loading", stopLoadingHandler); - webview.removeEventListener("focus", webviewFocus); - webview.removeEventListener("blur", webviewBlur); - webview.removeEventListener("dom-ready", handleDomReady); - }; + if (!webview) { + return; } + const navigateListener = (e: any) => { + model.handleNavigate(e.url); + }; + const newWindowHandler = (e: any) => { + e.preventDefault(); + const newUrl = e.detail.url; + console.log("webview new-window event:", newUrl); + fireAndForget(() => openLink(newUrl, true)); + }; + const startLoadingHandler = () => { + model.setRefreshIcon("xmark-large"); + model.setIsLoading(true); + webview.style.backgroundColor = "transparent"; + }; + const stopLoadingHandler = () => { + model.setRefreshIcon("rotate-right"); + model.setIsLoading(false); + setBgColor(); + }; + const failLoadHandler = (e: any) => { + if (e.errorCode === -3) { + console.warn("Suppressed ERR_ABORTED error", e); + } else { + console.error(`Failed to load ${e.validatedURL}: ${e.errorDescription}`); + } + }; + const webviewFocus = () => { + getApi().setWebviewFocus(webview.getWebContentsId()); + model.nodeModel.focusNode(); + }; + const webviewBlur = () => { + getApi().setWebviewFocus(null); + }; + const handleDomReady = () => { + setDomReady(true); + setBgColor(); + }; + + webview.addEventListener("did-navigate-in-page", navigateListener); + webview.addEventListener("did-navigate", navigateListener); + webview.addEventListener("did-start-loading", startLoadingHandler); + webview.addEventListener("did-stop-loading", stopLoadingHandler); + webview.addEventListener("new-window", newWindowHandler); + webview.addEventListener("did-fail-load", failLoadHandler); + webview.addEventListener("focus", webviewFocus); + webview.addEventListener("blur", webviewBlur); + webview.addEventListener("dom-ready", handleDomReady); + + // Clean up event listeners on component unmount + return () => { + webview.removeEventListener("did-navigate", navigateListener); + webview.removeEventListener("did-navigate-in-page", navigateListener); + webview.removeEventListener("new-window", newWindowHandler); + webview.removeEventListener("did-fail-load", failLoadHandler); + webview.removeEventListener("did-start-loading", startLoadingHandler); + webview.removeEventListener("did-stop-loading", stopLoadingHandler); + webview.removeEventListener("focus", webviewFocus); + webview.removeEventListener("blur", webviewBlur); + webview.removeEventListener("dom-ready", handleDomReady); + }; }, []); return ( diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index cc7296747..7736335b0 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -86,6 +86,9 @@ declare global { type?: "separator" | "normal" | "submenu" | "checkbox" | "radio"; submenu?: ElectronContextMenuItem[]; checked?: boolean; + visible?: boolean; + enabled?: boolean; + sublabel?: string; }; type ContextMenuItem = { @@ -95,6 +98,9 @@ declare global { click?: () => void; // not required if role is set submenu?: ContextMenuItem[]; checked?: boolean; + visible?: boolean; + enabled?: boolean; + sublabel?: string; }; type KeyPressDecl = {