Skip to content

Commit aa30586

Browse files
committed
feat: add webview render
1 parent e694d92 commit aa30586

File tree

10 files changed

+226
-22
lines changed

10 files changed

+226
-22
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
},
9898
"dependencies": {
9999
"@arco-design/web-react": "^2.44.1",
100+
"ahooks": "^3.7.5",
100101
"allotment": "^1.18.0",
101102
"electron-debug": "^3.2.0",
102103
"electron-log": "^4.4.8",

pnpm-lock.yaml

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { autoUpdater } from 'electron-updater';
1414
import log from 'electron-log';
1515
import MenuBuilder from './menu';
1616
import { resolveHtmlPath } from './util';
17+
import { initWebviewManager } from './webviewManager';
1718

1819
class AppUpdater {
1920
constructor() {
@@ -110,6 +111,8 @@ const createWindow = async () => {
110111
// Remove this if your app does not use auto updates
111112
// eslint-disable-next-line
112113
new AppUpdater();
114+
115+
initWebviewManager(mainWindow);
113116
};
114117

115118
/**

src/main/preload.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
/* eslint no-unused-vars: off */
33
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
44

5-
export type Channels = 'ipc-example';
5+
export type Channels = 'mount-webview' | 'update-webview-rect';
66

77
const electronHandler = {
88
ipcRenderer: {
9-
sendMessage(channel: Channels, args: unknown[]) {
10-
ipcRenderer.send(channel, args);
9+
sendMessage(channel: Channels, ...args: unknown[]) {
10+
ipcRenderer.send(channel, ...args);
1111
},
1212
on(channel: Channels, func: (...args: unknown[]) => void) {
1313
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>

src/main/webviewManager.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { BrowserView, BrowserWindow, ipcMain, Rectangle } from 'electron';
2+
3+
const webviewMap = new Map<string, BrowserView>();
4+
5+
/**
6+
* fix rect into correct size
7+
*/
8+
function fixRect(rect: Rectangle): Rectangle {
9+
return {
10+
x: Math.round(rect.x),
11+
y: Math.round(rect.y) + 28,
12+
width: Math.round(rect.width),
13+
height: Math.round(rect.height),
14+
};
15+
}
16+
17+
export function initWebviewManager(win: BrowserWindow) {
18+
ipcMain.on('mount-webview', (e, info) => {
19+
const key = info.key;
20+
const url = info.url;
21+
if (!url) {
22+
return;
23+
}
24+
25+
if (webviewMap.has(key)) {
26+
win.setTopBrowserView(webviewMap.get(key)!);
27+
return;
28+
}
29+
30+
console.log('[mount-webview] info:', info);
31+
32+
const view = new BrowserView({
33+
webPreferences: {
34+
nodeIntegration: false,
35+
},
36+
});
37+
win.addBrowserView(view);
38+
view.setBounds(fixRect(info.rect));
39+
view.webContents.loadURL(url);
40+
webviewMap.set(key, view);
41+
});
42+
43+
ipcMain.on('update-webview-rect', (e, info) => {
44+
const key = info.key;
45+
if (!webviewMap.has(key)) {
46+
return;
47+
}
48+
49+
console.log('[update-webview-rect] info:', info);
50+
51+
const view = webviewMap.get(key)!;
52+
view.setBounds(fixRect(info.rect));
53+
});
54+
}

src/renderer/components/SideTree.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Tree } from '@arco-design/web-react';
22
import { IconDown, IconPlus } from '@arco-design/web-react/icon';
33
import React from 'react';
4-
import { generateFakeNode, useTreeStore } from '../store/tree';
4+
import { generateFakeNode, WebsiteTreeNode, useTreeStore } from '../store/tree';
55
import { AddWebsiteBtn } from './AddWebsiteBtn';
66
import styled from 'styled-components';
77

@@ -12,7 +12,8 @@ const StyledTree = styled(Tree)`
1212
` as unknown as typeof Tree;
1313

1414
export const SideTree: React.FC = React.memo(() => {
15-
const { treeData, moveTreeNode, addTreeNodeChildren } = useTreeStore();
15+
const { treeData, moveTreeNode, setSelectedNode, addTreeNodeChildren } =
16+
useTreeStore();
1617

1718
return (
1819
<div>
@@ -25,6 +26,12 @@ export const SideTree: React.FC = React.memo(() => {
2526
icons={{
2627
switcherIcon: <IconDown />,
2728
}}
29+
onSelect={(_, extra) => {
30+
const nodeData = extra.node.props.dataRef;
31+
if (nodeData) {
32+
setSelectedNode(nodeData as WebsiteTreeNode);
33+
}
34+
}}
2835
renderExtra={(node) => {
2936
return (
3037
<IconPlus
Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,77 @@
1-
import React from 'react';
1+
import React, { useEffect, useRef } from 'react';
2+
import { useTreeStore, WebsiteTreeNode } from '../store/tree';
3+
4+
const WebviewRender: React.FC<{ node: WebsiteTreeNode }> = React.memo(
5+
(props) => {
6+
const containerRef = useRef<HTMLDivElement>(null);
7+
const node = props.node;
8+
9+
useEffect(() => {
10+
if (!containerRef.current) {
11+
return;
12+
}
13+
14+
const rect = containerRef.current.getBoundingClientRect();
15+
16+
window.electron.ipcRenderer.sendMessage('mount-webview', {
17+
key: node.key,
18+
url: node.url,
19+
rect: {
20+
x: rect.x,
21+
y: rect.y,
22+
width: rect.width,
23+
height: rect.height,
24+
},
25+
});
26+
}, [node.key]);
27+
28+
useEffect(() => {
29+
if (!containerRef.current) {
30+
return;
31+
}
32+
33+
const resizeObserver = new ResizeObserver((entries) => {
34+
entries.forEach((entry) => {
35+
const { target } = entry;
36+
if (!target.parentElement) {
37+
return;
38+
}
39+
40+
const rect = target.getBoundingClientRect();
41+
42+
window.electron.ipcRenderer.sendMessage('update-webview-rect', {
43+
key: node.key,
44+
rect: {
45+
x: rect.x,
46+
y: rect.y,
47+
width: rect.width,
48+
height: rect.height,
49+
},
50+
});
51+
});
52+
});
53+
54+
resizeObserver.observe(containerRef.current);
55+
56+
return () => {
57+
if (containerRef.current) {
58+
resizeObserver.unobserve(containerRef.current);
59+
}
60+
};
61+
}, [node.key]);
62+
63+
return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
64+
}
65+
);
66+
WebviewRender.displayName = 'WebviewRender';
267

368
export const WebContent: React.FC = React.memo(() => {
4-
return <div>fooooooooo</div>;
69+
const selectedNode = useTreeStore((state) => state.selectedNode);
70+
71+
if (!selectedNode) {
72+
return <div>Please Select Node</div>;
73+
}
74+
75+
return <WebviewRender node={selectedNode} />;
576
});
677
WebContent.displayName = 'WebContent';

src/renderer/index.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
http-equiv="Content-Security-Policy"
77
content="script-src 'self' 'unsafe-inline'"
88
/>
9-
<title>Hello Electron React!</title>
9+
<title>Webbox</title>
1010
</head>
1111
<body>
1212
<div id="root"></div>

src/renderer/preload.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ElectronHandler } from 'main/preload';
1+
import { ElectronHandler } from '../main/preload';
22

33
declare global {
44
// eslint-disable-next-line no-unused-vars

0 commit comments

Comments
 (0)