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