diff --git a/emain/emain.ts b/emain/emain.ts index b06764b14..f9011e362 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -281,6 +281,12 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrow }); win.webContents.on("will-navigate", shNavHandler); win.webContents.on("will-frame-navigate", shFrameNavHandler); + win.webContents.on("did-attach-webview", (event, wc) => { + wc.setWindowOpenHandler((details) => { + win.webContents.send("webview-new-window", wc.id, details); + return { action: "deny" }; + }); + }); win.on( "resize", debounce(400, (e) => mainResizeHandler(e, waveWindow.oid, win)) @@ -418,10 +424,21 @@ electron.ipcMain.on("isDevServer", (event) => { event.returnValue = isDevServer; }); -electron.ipcMain.on("getPlatform", (event) => { +electron.ipcMain.on("getPlatform", (event, url) => { event.returnValue = unamePlatform; }); +// Listen for the open-external event from the renderer process +electron.ipcMain.on("open-external", (event, url) => { + if (url && typeof url === "string") { + electron.shell.openExternal(url).catch((err) => { + console.error(`Failed to open URL ${url}:`, err); + }); + } else { + console.error("Invalid URL received in open-external event:", url); + } +}); + electron.ipcMain.on("download", (event, payload) => { const window = electron.BrowserWindow.fromWebContents(event.sender); const baseName = payload.filePath.split(/[\\/]/).pop(); @@ -535,7 +552,6 @@ async function appMain() { } const ready = await waveSrvReady; console.log("wavesrv ready signal received", ready, Date.now() - startTs, "ms"); - console.log("get client data"); let clientData = await services.ClientService.GetClientData(); console.log("client data ready"); diff --git a/emain/preload.ts b/emain/preload.ts index b1350bf90..1741a00c4 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -12,4 +12,17 @@ contextBridge.exposeInMainWorld("api", { showContextMenu: (menu, position) => ipcRenderer.send("contextmenu-show", menu, position), onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", callback), downloadFile: (filePath) => ipcRenderer.send("download", { filePath }), + openExternal: (url) => { + if (url && typeof url === "string") { + ipcRenderer.send("open-external", url); + } else { + console.error("Invalid URL passed to openExternal:", url); + } + }, +}); + +// Custom event for "new-window" +ipcRenderer.on("webview-new-window", (e, webContentsId, details) => { + const event = new CustomEvent("new-window", { detail: details }); + document.getElementById("webview").dispatchEvent(event); }); diff --git a/frontend/app/view/webview.tsx b/frontend/app/view/webview.tsx index afc5755fc..8808ba44d 100644 --- a/frontend/app/view/webview.tsx +++ b/frontend/app/view/webview.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Button } from "@/app/element/button"; +import { getApi } from "@/app/store/global"; import { WebviewTag } from "electron"; import React, { useEffect, useRef, useState } from "react"; @@ -23,10 +24,15 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => { const historyStack = useRef([]); const historyIndex = useRef(-1); - useEffect(() => { - const inputHeight = inputRef.current?.getBoundingClientRect().height + 25; + const getWebViewHeight = () => { + const inputHeight = inputRef.current?.getBoundingClientRect().height; const parentHeight = parentRef.current?.getBoundingClientRect().height; - setWebViewHeight(parentHeight - inputHeight); + return parentHeight - (inputHeight + 35); + }; + + useEffect(() => { + const webviewHeight = getWebViewHeight(); + setWebViewHeight(webviewHeight); historyStack.current.push(initialUrl); historyIndex.current = 0; @@ -57,8 +63,8 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => { // Handle new-window event webview.addEventListener("new-window", (event: any) => { event.preventDefault(); - const newUrl = event.url; - webview.src = newUrl; + const newUrl = event.detail.url; + getApi().openExternal(newUrl); }); // Suppress errors @@ -100,21 +106,31 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => { }; const handleResize = () => { - const parentHeight = parentRef.current?.getBoundingClientRect().height; - setWebViewHeight(parentHeight); + const webviewHeight = getWebViewHeight(); + setWebViewHeight(webviewHeight); }; const parentElement = parentRef.current; if (parentElement) { parentElement.addEventListener("keydown", handleKeyDown); } - window.addEventListener("resize", handleResize); + + // Use ResizeObserver to observe changes in the height of parentRef + const resizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + if (entry.target === parentElement) { + handleResize(); + } + } + }); + + resizeObserver.observe(parentElement); return () => { if (parentElement) { parentElement.removeEventListener("keydown", handleKeyDown); } - window.removeEventListener("resize", handleResize); + resizeObserver.disconnect(); }; }, []); @@ -221,7 +237,13 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => { /> - + ); }; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 870e1133c..98ad76da0 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -37,6 +37,7 @@ declare global { onNavigate: (callback: (url: string) => void) => void; onIframeNavigate: (callback: (url: string) => void) => void; downloadFile: (path: string) => void; + openExternal: (url: string) => void; }; type ElectronContextMenuItem = { diff --git a/package.json b/package.json index 2e63c88ee..dffe064ab 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "base64-js": "^1.5.1", "clsx": "^2.1.1", "dayjs": "^1.11.11", - "electron": "^30.1.0", + "electron": "^31.1.0", "html-to-image": "^1.11.11", "immer": "^10.1.1", "jotai": "^2.8.0", diff --git a/yarn.lock b/yarn.lock index 39c3d5d4e..feeb4de7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6499,16 +6499,16 @@ __metadata: languageName: node linkType: hard -"electron@npm:^30.1.0": - version: 30.1.0 - resolution: "electron@npm:30.1.0" +"electron@npm:^31.1.0": + version: 31.1.0 + resolution: "electron@npm:31.1.0" dependencies: "@electron/get": "npm:^2.0.0" "@types/node": "npm:^20.9.0" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: 10c0/e17c5fdf275f8cd457086adfa05db7507d08dd9264024d80eae8e55eb01927d062b63223a1a15b3ac024ebe2c34cc4b3ceb5cc0ac3ded4721c4ac9be4e5fe33c + checksum: 10c0/b9ae6f9d1a13e15cf0b7981f1231dc2c79f3465c4db822750daf6f2533524fe2ddf6f0b173c8d0c3549ef6bf21a9ff3eb362838b06deb4f795e71d5e5a307f16 languageName: node linkType: hard @@ -12336,7 +12336,7 @@ __metadata: base64-js: "npm:^1.5.1" clsx: "npm:^2.1.1" dayjs: "npm:^1.11.11" - electron: "npm:^30.1.0" + electron: "npm:^31.1.0" electron-vite: "npm:^2.2.0" eslint: "npm:^9.2.0" eslint-config-prettier: "npm:^9.1.0"