feat: add 'pop out tab as new window' functionality
- Add createWindow IPC for creating secondary windows - Add PopoutPage for content-only rendering in new windows - Add multi-window management to electron state - Add '在新窗口中打开' context menu to tabs - Fix: Use standard URL path instead of hash for React Router routing
This commit is contained in:
@@ -98,6 +98,65 @@ async function createWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
async function createSecondaryWindow(tabData: { route: string; title: string }): Promise<number> {
|
||||
const initialSymbolColor = nativeTheme.shouldUseDarkColors ? '#ffffff' : '#000000';
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
autoHideMenuBar: true,
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#00000000',
|
||||
symbolColor: initialSymbolColor,
|
||||
height: 32,
|
||||
},
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
webviewTag: true,
|
||||
preload: path.join(__dirname, 'preload.cjs'),
|
||||
},
|
||||
});
|
||||
|
||||
electronState.addWindow(win);
|
||||
win.setMenu(null);
|
||||
|
||||
win.on('closed', () => {
|
||||
electronState.removeWindow(win.id);
|
||||
});
|
||||
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith('http:') || url.startsWith('https:')) {
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
}
|
||||
return { action: 'allow' };
|
||||
});
|
||||
|
||||
const baseUrl = electronState.isDevelopment()
|
||||
? 'http://localhost:5173'
|
||||
: `http://localhost:${electronState.getServerPort()}`;
|
||||
|
||||
const fullUrl = `${baseUrl}${tabData.route}`;
|
||||
log.info(`[PopOut] Loading secondary window with URL: ${fullUrl}`);
|
||||
|
||||
try {
|
||||
await win.loadURL(fullUrl);
|
||||
} catch (e) {
|
||||
log.error('[PopOut] Failed to load URL:', e);
|
||||
}
|
||||
|
||||
if (electronState.isDevelopment()) {
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
|
||||
return win.id;
|
||||
}
|
||||
|
||||
ipcMain.handle('export-pdf', async (event, title, htmlContent) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (!win) return { success: false, error: 'No window found' };
|
||||
@@ -421,6 +480,17 @@ ipcMain.handle('terminal-stop', async () => {
|
||||
return await terminalService.stop();
|
||||
});
|
||||
|
||||
ipcMain.handle('create-window', async (_event, tabData: { route: string; title: string }) => {
|
||||
try {
|
||||
log.info('[PopOut] Creating new window for:', tabData);
|
||||
const windowId = await createSecondaryWindow(tabData);
|
||||
return { success: true, windowId };
|
||||
} catch (error: any) {
|
||||
log.error('[PopOut] Failed to create window:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
async function startServer() {
|
||||
if (electronState.isDevelopment()) {
|
||||
log.info('In dev mode, assuming external servers are running.');
|
||||
|
||||
@@ -53,4 +53,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
terminalStop: () => ipcRenderer.invoke('terminal-stop'),
|
||||
terminalGetStatus: () => ipcRenderer.invoke('terminal-get-status'),
|
||||
terminalGetPort: () => ipcRenderer.invoke('terminal-get-port'),
|
||||
createWindow: (tabData: { route: string; title: string }) => ipcRenderer.invoke('create-window', tabData),
|
||||
})
|
||||
|
||||
@@ -13,6 +13,8 @@ class ElectronState {
|
||||
isDev: false,
|
||||
}
|
||||
|
||||
private windows = new Map<number, BrowserWindow>()
|
||||
|
||||
getMainWindow(): BrowserWindow | null {
|
||||
return this.state.mainWindow
|
||||
}
|
||||
@@ -37,7 +39,24 @@ class ElectronState {
|
||||
this.state.isDev = isDev
|
||||
}
|
||||
|
||||
addWindow(window: BrowserWindow): void {
|
||||
this.windows.set(window.id, window)
|
||||
}
|
||||
|
||||
removeWindow(id: number): void {
|
||||
this.windows.delete(id)
|
||||
}
|
||||
|
||||
getWindow(id: number): BrowserWindow | undefined {
|
||||
return this.windows.get(id)
|
||||
}
|
||||
|
||||
getAllWindows(): BrowserWindow[] {
|
||||
return Array.from(this.windows.values())
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.windows.clear()
|
||||
this.state = {
|
||||
mainWindow: null,
|
||||
serverPort: 3001,
|
||||
|
||||
Reference in New Issue
Block a user