mirror of https://github.com/msgbyte/tailchat
feat: add electron native webview render support #152
all website can be allow to visit in electronpull/147/merge
parent
af16ebe47b
commit
146952d4f3
@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Fork from https://github.com/msgbyte/webbox/blob/main/src/main/webviewManager.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BrowserView, BrowserWindow, ipcMain, Rectangle } from 'electron';
|
||||||
|
import os from 'os';
|
||||||
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
interface WebviewInfo {
|
||||||
|
view: BrowserView;
|
||||||
|
url: string;
|
||||||
|
hidden: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webviewMap = new Map<string, WebviewInfo>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fix rect into correct size
|
||||||
|
*/
|
||||||
|
function fixRect(rect: Rectangle, isFullScreen: boolean): Rectangle {
|
||||||
|
const xOffset = 1;
|
||||||
|
const yOffset = !isFullScreen && os.platform() === 'darwin' ? 28 : 0; // add y axis offset in mac os if is not fullScreen
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: Math.round(rect.x) + xOffset,
|
||||||
|
y: Math.round(rect.y) + yOffset,
|
||||||
|
width: Math.round(rect.width) - xOffset,
|
||||||
|
height: Math.round(rect.height),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initWebviewManager(win: BrowserWindow) {
|
||||||
|
ipcMain.on('$mount-webview', (e, info) => {
|
||||||
|
if (!win) {
|
||||||
|
log.info('[mount-webview]', 'cannot get mainWindow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('[mount-webview] info:', info);
|
||||||
|
|
||||||
|
const { key, url } = info;
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webviewMap.has(key)) {
|
||||||
|
const webview = webviewMap.get(key)!;
|
||||||
|
win.setTopBrowserView(webview.view);
|
||||||
|
webview.view.setBounds(fixRect(info.rect, win.isFullScreen()));
|
||||||
|
if (webview.url !== url) {
|
||||||
|
// url has been change.
|
||||||
|
webview.view.webContents.loadURL(url);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hideAllWebview();
|
||||||
|
const view = new BrowserView({
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
view.setBackgroundColor('#fff');
|
||||||
|
view.setBounds(fixRect(info.rect, win.isFullScreen()));
|
||||||
|
view.webContents.loadURL(url);
|
||||||
|
win.addBrowserView(view);
|
||||||
|
webviewMap.set(key, { view, url, hidden: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$unmount-webview', (e, info) => {
|
||||||
|
if (!win) {
|
||||||
|
log.info('[unmount-webview]', 'cannot get mainWindow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('[unmount-webview] info:', info);
|
||||||
|
|
||||||
|
const { key } = info;
|
||||||
|
const webview = webviewMap.get(key);
|
||||||
|
if (webview) {
|
||||||
|
win.removeBrowserView(webview.view);
|
||||||
|
webviewMap.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$update-webview-rect', (e, info) => {
|
||||||
|
if (!win) {
|
||||||
|
log.info('[update-webview-rect]', 'cannot get mainWindow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('[update-webview-rect] info:', info);
|
||||||
|
|
||||||
|
// Change All View to avoid under view display on resize.
|
||||||
|
// webviewMap.forEach((webview) => {
|
||||||
|
// webview.hidden = false;
|
||||||
|
// webview.view.setBounds(fixRect(info.rect, win.isFullScreen()));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Change Single View
|
||||||
|
const webview = webviewMap.get(info.key);
|
||||||
|
if (webview) {
|
||||||
|
webview.hidden = false;
|
||||||
|
webview.view.setBounds(fixRect(info.rect, win.isFullScreen()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$show-webview', (e, info) => {
|
||||||
|
log.info('[show-webview] info:', info);
|
||||||
|
|
||||||
|
const webview = webviewMap.get(info.key);
|
||||||
|
if (webview) {
|
||||||
|
showWebView(webview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$hide-webview', (e, info) => {
|
||||||
|
log.info('[hide-webview] info:', info);
|
||||||
|
|
||||||
|
const webview = webviewMap.get(info.key);
|
||||||
|
if (webview) {
|
||||||
|
hideWebView(webview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$hide-all-webview', () => {
|
||||||
|
log.info('[hide-all-webview]');
|
||||||
|
|
||||||
|
hideAllWebview();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('$clear-all-webview', () => {
|
||||||
|
if (!win) {
|
||||||
|
log.info('[clear-all-webview]', 'cannot get mainWindow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('[clear-all-webview]');
|
||||||
|
|
||||||
|
win.getBrowserViews().forEach((view) => {
|
||||||
|
win.removeBrowserView(view);
|
||||||
|
});
|
||||||
|
|
||||||
|
webviewMap.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const HIDDEN_OFFSET = 3000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show webview with remove offset in y
|
||||||
|
*/
|
||||||
|
function showWebView(webview: WebviewInfo) {
|
||||||
|
if (webview.hidden === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.hidden = false;
|
||||||
|
const oldBounds = webview.view.getBounds();
|
||||||
|
webview.view.setBounds({
|
||||||
|
...oldBounds,
|
||||||
|
y: oldBounds.y - HIDDEN_OFFSET,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide webview with append offset in y
|
||||||
|
*/
|
||||||
|
function hideWebView(webview: WebviewInfo) {
|
||||||
|
if (webview.hidden === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.hidden = true;
|
||||||
|
const oldBounds = webview.view.getBounds();
|
||||||
|
webview.view.setBounds({
|
||||||
|
...oldBounds,
|
||||||
|
y: oldBounds.y + HIDDEN_OFFSET,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAllWebview() {
|
||||||
|
Array.from(webviewMap.values()).forEach((webview) => {
|
||||||
|
hideWebView(webview);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
interface ElectronWebviewProps {
|
||||||
|
className?: string;
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
export const ElectronWebview: React.FC<ElectronWebviewProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const key = props.src;
|
||||||
|
const url = props.src;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
(window as any).electron.ipcRenderer.sendMessage('$mount-webview', {
|
||||||
|
key,
|
||||||
|
url,
|
||||||
|
rect: {
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
(window as any).electron.ipcRenderer.sendMessage('$unmount-webview', {
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [key, url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersectionObserver = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry: any) => {
|
||||||
|
if (entry.isVisible === true) {
|
||||||
|
// 完全可见,显示
|
||||||
|
(window as any).electron.ipcRenderer.sendMessage(
|
||||||
|
'$show-webview',
|
||||||
|
{
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
(window as any).electron.ipcRenderer.sendMessage(
|
||||||
|
'$hide-webview',
|
||||||
|
{
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trackVisibility: true,
|
||||||
|
delay: 200,
|
||||||
|
} as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const { target } = entry;
|
||||||
|
if (!target.parentElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
(window as any).electron.ipcRenderer.sendMessage(
|
||||||
|
'$update-webview-rect',
|
||||||
|
{
|
||||||
|
key: key,
|
||||||
|
rect: {
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
intersectionObserver.observe(containerRef.current);
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
intersectionObserver.unobserve(containerRef.current);
|
||||||
|
resizeObserver.unobserve(containerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [key]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={props.className}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ElectronWebview.displayName = 'ElectronWebview';
|
@ -0,0 +1,3 @@
|
|||||||
|
.ant-dropdown-menu {
|
||||||
|
box-shadow: none; /* avoid group detail dropdown's shadow will make dom invisiable */
|
||||||
|
}
|
Loading…
Reference in New Issue