diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 4caf9ce3..0ffb64b8 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -7,7 +7,15 @@ export default defineConfig({ plugins: [externalizeDepsPlugin()] }, preload: { - plugins: [externalizeDepsPlugin()] + plugins: [externalizeDepsPlugin()], + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'src/preload/index.ts'), + minapp: resolve(__dirname, 'src/preload/minapp.ts') + } + } + } }, renderer: { resolve: { diff --git a/resources/minapp.html b/resources/minapp.html new file mode 100644 index 00000000..164ce2e8 --- /dev/null +++ b/resources/minapp.html @@ -0,0 +1,65 @@ + + + + + + MinApp + + + +
+
+
MinApp
+
+
+ + diff --git a/src/main/index.ts b/src/main/index.ts index dbe37843..78128905 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,84 +1,12 @@ -import { electronApp, is, optimizer } from '@electron-toolkit/utils' +import { electronApp, optimizer } from '@electron-toolkit/utils' import * as Sentry from '@sentry/electron/main' -import { app, BrowserWindow, ipcMain, Menu, MenuItem, session, shell } from 'electron' +import { app, BrowserWindow, ipcMain, session, shell } from 'electron' import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer' -import windowStateKeeper from 'electron-window-state' -import { join } from 'path' -import icon from '../../build/icon.png?asset' import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config' import { saveFile } from './event' import AppUpdater from './updater' - -function createWindow() { - // Load the previous state with fallback to defaults - const mainWindowState = windowStateKeeper({ - defaultWidth: 1080, - defaultHeight: 670 - }) - - const theme = appConfig.get('theme') || 'light' - - // Create the browser window. - const mainWindow = new BrowserWindow({ - x: mainWindowState.x, - y: mainWindowState.y, - width: mainWindowState.width, - height: mainWindowState.height, - minWidth: 1080, - minHeight: 600, - show: true, - autoHideMenuBar: true, - transparent: process.platform === 'darwin', - vibrancy: 'fullscreen-ui', - titleBarStyle: 'hidden', - titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight, - trafficLightPosition: { x: 8, y: 12 }, - ...(process.platform === 'linux' ? { icon } : {}), - webPreferences: { - preload: join(__dirname, '../preload/index.js'), - sandbox: false, - webSecurity: false - // devTools: !app.isPackaged, - } - }) - - mainWindowState.manage(mainWindow) - - mainWindow.webContents.on('context-menu', () => { - const menu = new Menu() - menu.append(new MenuItem({ label: '复制', role: 'copy', sublabel: '⌘ + C' })) - menu.append(new MenuItem({ label: '粘贴', role: 'paste', sublabel: '⌘ + V' })) - menu.append(new MenuItem({ label: '剪切', role: 'cut', sublabel: '⌘ + X' })) - menu.append(new MenuItem({ type: 'separator' })) - menu.append(new MenuItem({ label: '全选', role: 'selectAll', sublabel: '⌘ + A' })) - menu.popup() - }) - - mainWindow.webContents.on('will-navigate', (event, url) => { - event.preventDefault() - shell.openExternal(url) - }) - - mainWindow.on('ready-to-show', () => { - mainWindow.show() - }) - - mainWindow.webContents.setWindowOpenHandler((details) => { - shell.openExternal(details.url) - return { action: 'deny' } - }) - - // HMR for renderer base on electron-vite cli. - // Load the remote URL for development or the local html file for production. - if (is.dev && process.env['ELECTRON_RENDERER_URL']) { - mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) - } else { - mainWindow.loadFile(join(__dirname, '../renderer/index.html')) - } - - return mainWindow -} +import { createMainWindow, createMinappWindow } from './window' // This method will be called when Electron has finished // initialization and is ready to create browser windows. @@ -97,10 +25,10 @@ app.whenReady().then(() => { app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) createWindow() + if (BrowserWindow.getAllWindows().length === 0) createMainWindow() }) - const mainWindow = createWindow() + const mainWindow = createMainWindow() const { autoUpdater } = new AppUpdater(mainWindow) @@ -121,6 +49,8 @@ app.whenReady().then(() => { ipcMain.handle('save-file', saveFile) + ipcMain.handle('minapp', (_, url: string) => createMinappWindow(url)) + ipcMain.handle('set-theme', (_, theme: 'light' | 'dark') => { appConfig.set('theme', theme) mainWindow?.setTitleBarOverlay && diff --git a/src/main/window.ts b/src/main/window.ts new file mode 100644 index 00000000..eadb8492 --- /dev/null +++ b/src/main/window.ts @@ -0,0 +1,115 @@ +import { is } from '@electron-toolkit/utils' +import { app, BrowserView, BrowserWindow, Menu, MenuItem, shell } from 'electron' +import windowStateKeeper from 'electron-window-state' +import { join } from 'path' + +import icon from '../../build/icon.png?asset' +import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config' + +export function createMainWindow() { + // Load the previous state with fallback to defaults + const mainWindowState = windowStateKeeper({ + defaultWidth: 1080, + defaultHeight: 670 + }) + + const theme = appConfig.get('theme') || 'light' + + // Create the browser window. + const mainWindow = new BrowserWindow({ + x: mainWindowState.x, + y: mainWindowState.y, + width: mainWindowState.width, + height: mainWindowState.height, + minWidth: 1080, + minHeight: 600, + show: true, + autoHideMenuBar: true, + transparent: process.platform === 'darwin', + vibrancy: 'fullscreen-ui', + titleBarStyle: 'hidden', + titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight, + trafficLightPosition: { x: 8, y: 12 }, + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + sandbox: false, + webSecurity: false + // devTools: !app.isPackaged, + } + }) + + mainWindowState.manage(mainWindow) + + mainWindow.webContents.on('context-menu', () => { + const menu = new Menu() + menu.append(new MenuItem({ label: '复制', role: 'copy', sublabel: '⌘ + C' })) + menu.append(new MenuItem({ label: '粘贴', role: 'paste', sublabel: '⌘ + V' })) + menu.append(new MenuItem({ label: '剪切', role: 'cut', sublabel: '⌘ + X' })) + menu.append(new MenuItem({ type: 'separator' })) + menu.append(new MenuItem({ label: '全选', role: 'selectAll', sublabel: '⌘ + A' })) + menu.popup() + }) + + mainWindow.on('ready-to-show', () => { + mainWindow.show() + }) + + mainWindow.webContents.on('will-navigate', (event, url) => { + event.preventDefault() + shell.openExternal(url) + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } + + return mainWindow +} + +export function createMinappWindow(url) { + const width = 500 + const height = 800 + const headerHeight = 40 + + const minappWindow = new BrowserWindow({ + width, + height, + autoHideMenuBar: true, + alwaysOnTop: true, + titleBarOverlay: titleBarOverlayDark, + titleBarStyle: 'hidden', + webPreferences: { + preload: join(__dirname, '../preload/minapp.js'), + sandbox: false + } + }) + + minappWindow.loadFile(app.getAppPath() + '/resources/minapp.html') + + const view = new BrowserView() + + minappWindow.setBrowserView(view) + view.setBounds({ x: 0, y: headerHeight, width, height: height - headerHeight }) + view.webContents.loadURL(url) + + minappWindow.on('resize', () => { + view.setBounds({ + x: 0, + y: headerHeight, + width: minappWindow.getBounds().width, + height: minappWindow.getBounds().height - headerHeight + }) + }) + + return minappWindow +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index e7183355..22e25b25 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -14,6 +14,7 @@ declare global { setProxy: (proxy: string | undefined) => void saveFile: (path: string, content: string) => void setTheme: (theme: 'light' | 'dark') => void + minApp: (url: string) => void } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 4b3a9c65..885fb4e3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -8,7 +8,8 @@ const api = { openWebsite: (url: string) => ipcRenderer.invoke('open-website', url), setProxy: (proxy: string) => ipcRenderer.invoke('set-proxy', proxy), saveFile: (path: string, content: string) => ipcRenderer.invoke('save-file', path, content), - setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme) + setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme), + minApp: (url: string) => ipcRenderer.invoke('minapp', url) } // Use `contextBridge` APIs to expose Electron APIs to diff --git a/src/preload/minapp.ts b/src/preload/minapp.ts new file mode 100644 index 00000000..9ace85b5 --- /dev/null +++ b/src/preload/minapp.ts @@ -0,0 +1,14 @@ +import { contextBridge } from 'electron' + +const api = {} + +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('api', api) + } catch (error) { + console.error(error) + } +} else { + // @ts-ignore (define in dts) + window.api = api +}