优化
This commit is contained in:
11
client/web/plugins/com.msgbyte.env.electron/manifest.json
Normal file
11
client/web/plugins/com.msgbyte.env.electron/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"label": "Electron Support",
|
||||
"label.zh-CN": "Electron 支持",
|
||||
"name": "com.msgbyte.env.electron",
|
||||
"url": "/plugins/com.msgbyte.env.electron/index.js",
|
||||
"version": "0.0.0",
|
||||
"author": "moonrailgun",
|
||||
"description": "Add support for Electron environment in Tailchat",
|
||||
"description.zh-CN": "在 Tailchat 添加对 Electron 环境的支持",
|
||||
"requireRestart": true
|
||||
}
|
||||
19
client/web/plugins/com.msgbyte.env.electron/package.json
Normal file
19
client/web/plugins/com.msgbyte.env.electron/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@plugins/com.msgbyte.env.electron",
|
||||
"main": "src/index.tsx",
|
||||
"version": "0.0.0",
|
||||
"description": "Add support for Electron environment in Tailchat",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"sync:declaration": "tailchat declaration github"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/semver": "^7.5.0",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"react": "18.2.0",
|
||||
"styled-components": "^5.3.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { showSuccessToasts } from '@capital/common';
|
||||
import { Button } from '@capital/component';
|
||||
import React from 'react';
|
||||
import { checkUpdate } from './checkUpdate';
|
||||
import { Translate } from './translate';
|
||||
import { getDeviceInfo } from './utils';
|
||||
|
||||
export const DeviceInfoPanel: React.FC = React.memo(() => {
|
||||
const deviceInfo = getDeviceInfo();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{Translate.clientName}: {deviceInfo.name}
|
||||
</div>
|
||||
<div>
|
||||
{Translate.clientVersion}: {deviceInfo.version}
|
||||
</div>
|
||||
<div>
|
||||
{Translate.platform}: {deviceInfo.platform}
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const res = await checkUpdate();
|
||||
if (res === false) {
|
||||
showSuccessToasts(Translate.isLatest);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{Translate.checkVersion}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
DeviceInfoPanel.displayName = 'DeviceInfoPanel';
|
||||
@@ -0,0 +1,121 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Translate } from './translate';
|
||||
|
||||
interface ElectronWebviewProps {
|
||||
className?: string;
|
||||
src: string;
|
||||
}
|
||||
export const ElectronWebview: React.FC<ElectronWebviewProps> = React.memo(
|
||||
(props) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isVisiable, setIsVisiable] = useState(true);
|
||||
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,
|
||||
}
|
||||
);
|
||||
}
|
||||
setIsVisiable(entry.isVisible);
|
||||
});
|
||||
},
|
||||
{
|
||||
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%' }}
|
||||
>
|
||||
{isVisiable === false && (
|
||||
<span>{Translate.nativeWebviewRenderHideTip}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
ElectronWebview.displayName = 'ElectronWebview';
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Button, notification } from '@capital/component';
|
||||
import React from 'react';
|
||||
import { Translate } from './translate';
|
||||
import { getDeviceInfo } from './utils';
|
||||
|
||||
const url = 'https://tailchat.msgbyte.com/downloads/client.json';
|
||||
|
||||
export async function checkUpdate(): Promise<boolean> {
|
||||
const deviceInfo = getDeviceInfo();
|
||||
const [semver, config] = await Promise.all([
|
||||
import('semver'),
|
||||
fetch(url).then((res) => res.json()),
|
||||
]);
|
||||
|
||||
const version = deviceInfo.version;
|
||||
const platform = deviceInfo.platform;
|
||||
const latestConfig = config?.[platform];
|
||||
const latestVersion = latestConfig?.version;
|
||||
const latestUrl = latestConfig?.url;
|
||||
|
||||
if (!latestVersion) {
|
||||
console.warn('Not found latest version');
|
||||
return true;
|
||||
}
|
||||
if (!latestUrl) {
|
||||
console.warn('Not found latest url');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (version === '0.0.0') {
|
||||
console.warn('Current version not valid');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (semver.gt(latestVersion, version)) {
|
||||
// 有新版本
|
||||
notification.info({
|
||||
message: Translate.newVersionTip,
|
||||
description: (
|
||||
<div>
|
||||
<div>{Translate.newVersionDesc}</div>
|
||||
<div>
|
||||
{version} -> {latestVersion}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
btn: (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.open(latestUrl);
|
||||
}}
|
||||
>
|
||||
{Translate.upgradeNow}
|
||||
</Button>
|
||||
),
|
||||
placement: 'topRight',
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
101
client/web/plugins/com.msgbyte.env.electron/src/index.tsx
Normal file
101
client/web/plugins/com.msgbyte.env.electron/src/index.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
regCustomPanel,
|
||||
regChatInputButton,
|
||||
postMessageEvent,
|
||||
sharedEvent,
|
||||
regPluginSettings,
|
||||
getCachedUserSettings,
|
||||
} from '@capital/common';
|
||||
import { Icon } from '@capital/component';
|
||||
import React from 'react';
|
||||
import { DeviceInfoPanel } from './DeviceInfoPanel';
|
||||
import { Translate } from './translate';
|
||||
import { forwardSharedEvent } from './utils';
|
||||
import { checkUpdate } from './checkUpdate';
|
||||
import { setWebviewKernel, resetWebviewKernel } from '@capital/common';
|
||||
import { ElectronWebview } from './ElectronWebview';
|
||||
import './overwrite.css';
|
||||
|
||||
const PLUGIN_NAME = 'Electron Support';
|
||||
const WEBVIEW_CONFIG = 'electron:nativeWebviewRender';
|
||||
|
||||
console.log(`Plugin ${PLUGIN_NAME} is loaded`);
|
||||
|
||||
regCustomPanel({
|
||||
position: 'setting',
|
||||
icon: '',
|
||||
name: 'com.msgbyte.env.electron/deviceInfoPanel',
|
||||
label: Translate.deviceInfo,
|
||||
render: DeviceInfoPanel,
|
||||
});
|
||||
|
||||
regChatInputButton({
|
||||
render: () => {
|
||||
return (
|
||||
<Icon
|
||||
className="text-2xl cursor-pointer"
|
||||
icon="mdi:content-cut"
|
||||
rotate={3}
|
||||
onClick={() => {
|
||||
postMessageEvent('callScreenshotsTool');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
regPluginSettings({
|
||||
position: 'system',
|
||||
type: 'boolean',
|
||||
name: WEBVIEW_CONFIG,
|
||||
label: Translate.nativeWebviewRender,
|
||||
desc: Translate.nativeWebviewRenderDesc,
|
||||
});
|
||||
|
||||
forwardSharedEvent('receiveUnmutedMessage');
|
||||
|
||||
setTimeout(() => {
|
||||
checkUpdate();
|
||||
}, 1000);
|
||||
|
||||
let changedWithElectron = false;
|
||||
|
||||
const checkSettingConfig = (settings: Record<string, any>) => {
|
||||
if (settings[WEBVIEW_CONFIG] === true) {
|
||||
setWebviewKernel(() => ElectronWebview);
|
||||
changedWithElectron = true;
|
||||
} else if (changedWithElectron === true) {
|
||||
// 如果关闭了配置且仅当之前用electron设置了webview,则重置
|
||||
resetWebviewKernel();
|
||||
}
|
||||
};
|
||||
|
||||
sharedEvent.on('loginSuccess', () => {
|
||||
getCachedUserSettings().then((settings) => {
|
||||
checkSettingConfig(settings);
|
||||
});
|
||||
});
|
||||
|
||||
sharedEvent.on('userSettingsUpdate', (settings) => {
|
||||
checkSettingConfig(settings);
|
||||
});
|
||||
|
||||
navigator.mediaDevices.getDisplayMedia = async (
|
||||
options: DisplayMediaStreamOptions
|
||||
) => {
|
||||
const source = await (
|
||||
window as any
|
||||
).electron.ipcRenderer.getDesktopCapturerSource();
|
||||
|
||||
const stream = await window.navigator.mediaDevices.getUserMedia({
|
||||
// audio: options.audio,
|
||||
video: {
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: source.id,
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.ant-dropdown-menu {
|
||||
box-shadow: none; /* avoid group detail dropdown's shadow will make dom invisiable */
|
||||
}
|
||||
53
client/web/plugins/com.msgbyte.env.electron/src/translate.ts
Normal file
53
client/web/plugins/com.msgbyte.env.electron/src/translate.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { localTrans } from '@capital/common';
|
||||
|
||||
export const Translate = {
|
||||
deviceInfo: localTrans({
|
||||
'zh-CN': '设备信息',
|
||||
'en-US': 'Device Info',
|
||||
}),
|
||||
clientName: localTrans({
|
||||
'zh-CN': '客户端名称',
|
||||
'en-US': 'Client Name',
|
||||
}),
|
||||
clientVersion: localTrans({
|
||||
'zh-CN': '客户端版本号',
|
||||
'en-US': 'Client Version',
|
||||
}),
|
||||
platform: localTrans({
|
||||
'zh-CN': '平台',
|
||||
'en-US': 'Platform',
|
||||
}),
|
||||
newVersionTip: localTrans({
|
||||
'zh-CN': '新版本提示',
|
||||
'en-US': 'New Version Upgrade Tip',
|
||||
}),
|
||||
newVersionDesc: localTrans({
|
||||
'zh-CN': '发现有新的版本可以安装',
|
||||
'en-US': 'A new version was found to be installed',
|
||||
}),
|
||||
upgradeNow: localTrans({
|
||||
'zh-CN': '立即更新',
|
||||
'en-US': 'Upgrade Now',
|
||||
}),
|
||||
checkVersion: localTrans({
|
||||
'zh-CN': '检查更新',
|
||||
'en-US': 'Check for updates',
|
||||
}),
|
||||
isLatest: localTrans({
|
||||
'zh-CN': '已经是最新版',
|
||||
'en-US': 'Already the latest version',
|
||||
}),
|
||||
nativeWebviewRender: localTrans({
|
||||
'zh-CN': '启用原生浏览器内核渲染',
|
||||
'en-US': 'Use Native Webview Render',
|
||||
}),
|
||||
nativeWebviewRenderDesc: localTrans({
|
||||
'zh-CN': '解除默认网页访问限制,允许在Tailchat嵌入任意网站内容',
|
||||
'en-US':
|
||||
'Lift default web page access restrictions and allow any website content to be embedded in Tailchat',
|
||||
}),
|
||||
nativeWebviewRenderHideTip: localTrans({
|
||||
'zh-CN': '组件被遮挡,暂时隐藏网页视图',
|
||||
'en-US': 'The component is obscured, temporarily hiding the web view',
|
||||
}),
|
||||
};
|
||||
41
client/web/plugins/com.msgbyte.env.electron/src/utils.ts
Normal file
41
client/web/plugins/com.msgbyte.env.electron/src/utils.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { sharedEvent, postMessageEvent } from '@capital/common';
|
||||
|
||||
/**
|
||||
* 转发事件
|
||||
*/
|
||||
export function forwardSharedEvent(
|
||||
eventName: string,
|
||||
processPayload?: (payload: any) => Promise<{ type: string; payload: any }>
|
||||
) {
|
||||
sharedEvent.on(eventName as any, async (payload: any) => {
|
||||
let type = eventName;
|
||||
if (processPayload) {
|
||||
const res = await processPayload(payload);
|
||||
if (!res) {
|
||||
// Skip if res is undefined
|
||||
return;
|
||||
}
|
||||
|
||||
payload = res.payload;
|
||||
type = res.type;
|
||||
}
|
||||
|
||||
postMessageEvent(type, payload);
|
||||
});
|
||||
}
|
||||
|
||||
interface ElectronDeviceInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
platform: string;
|
||||
}
|
||||
export function getDeviceInfo() {
|
||||
const deviceInfo: ElectronDeviceInfo = {
|
||||
name: '',
|
||||
version: '0.0.0',
|
||||
platform: 'windows',
|
||||
...((window as any).__electronDeviceInfo ?? {}),
|
||||
};
|
||||
|
||||
return deviceInfo;
|
||||
}
|
||||
10
client/web/plugins/com.msgbyte.env.electron/tsconfig.json
Normal file
10
client/web/plugins/com.msgbyte.env.electron/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"importsNotUsedAsValues": "error",
|
||||
"paths": {
|
||||
"@capital/*": ["../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user