feat: add minapp window

This commit is contained in:
kangfenmao 2024-08-17 13:30:54 +08:00
parent e43f7f87ab
commit 1996e163c9
7 changed files with 213 additions and 79 deletions

View File

@ -7,7 +7,15 @@ export default defineConfig({
plugins: [externalizeDepsPlugin()] plugins: [externalizeDepsPlugin()]
}, },
preload: { preload: {
plugins: [externalizeDepsPlugin()] plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/preload/index.ts'),
minapp: resolve(__dirname, 'src/preload/minapp.ts')
}
}
}
}, },
renderer: { renderer: {
resolve: { resolve: {

65
resources/minapp.html Normal file
View File

@ -0,0 +1,65 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MinApp</title>
<style>
html,
body {
margin: 0;
padding: 0;
}
header {
height: 40px;
background-color: #303030;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
-webkit-app-region: drag;
}
.header-right {
margin-left: auto;
margin-right: 10px;
display: flex;
flex-direction: row;
align-items: center;
}
.header-left {
margin-left: 10px;
margin-right: auto;
}
.header-center {
color: #fff;
font-size: 14px;
margin-left: 10px;
}
button {
background: none;
border: none;
color: white;
cursor: pointer;
width: 26px;
height: 26px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 14px;
border-radius: 3px;
-webkit-app-region: no-drag;
}
button:hover {
background-color: #555;
}
</style>
</head>
<body>
<header>
<div class="header-left"></div>
<div class="header-center">MinApp</div>
<div class="header-right"></div>
</header>
</body>
</html>

View File

@ -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 * 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 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 { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config'
import { saveFile } from './event' import { saveFile } from './event'
import AppUpdater from './updater' import AppUpdater from './updater'
import { createMainWindow, createMinappWindow } from './window'
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
}
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
@ -97,10 +25,10 @@ app.whenReady().then(() => {
app.on('activate', function () { app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the // 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. // 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) const { autoUpdater } = new AppUpdater(mainWindow)
@ -121,6 +49,8 @@ app.whenReady().then(() => {
ipcMain.handle('save-file', saveFile) ipcMain.handle('save-file', saveFile)
ipcMain.handle('minapp', (_, url: string) => createMinappWindow(url))
ipcMain.handle('set-theme', (_, theme: 'light' | 'dark') => { ipcMain.handle('set-theme', (_, theme: 'light' | 'dark') => {
appConfig.set('theme', theme) appConfig.set('theme', theme)
mainWindow?.setTitleBarOverlay && mainWindow?.setTitleBarOverlay &&

115
src/main/window.ts Normal file
View File

@ -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
}

View File

@ -14,6 +14,7 @@ declare global {
setProxy: (proxy: string | undefined) => void setProxy: (proxy: string | undefined) => void
saveFile: (path: string, content: string) => void saveFile: (path: string, content: string) => void
setTheme: (theme: 'light' | 'dark') => void setTheme: (theme: 'light' | 'dark') => void
minApp: (url: string) => void
} }
} }
} }

View File

@ -8,7 +8,8 @@ const api = {
openWebsite: (url: string) => ipcRenderer.invoke('open-website', url), openWebsite: (url: string) => ipcRenderer.invoke('open-website', url),
setProxy: (proxy: string) => ipcRenderer.invoke('set-proxy', proxy), setProxy: (proxy: string) => ipcRenderer.invoke('set-proxy', proxy),
saveFile: (path: string, content: string) => ipcRenderer.invoke('save-file', path, content), 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 // Use `contextBridge` APIs to expose Electron APIs to

14
src/preload/minapp.ts Normal file
View File

@ -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
}