feat(stage-tamagotchi): support detached devtools window with params

This commit is contained in:
Makito
2026-04-14 02:21:10 +09:00
parent 826861f9c9
commit 3d35d492ba
6 changed files with 75 additions and 47 deletions
+2 -2
View File
@@ -139,7 +139,7 @@ app.whenReady().then(async () => {
// BeatSync will create a background window to capture and process audio.
const beatSync = injeca.provide('windows:beat-sync', () => setupBeatSync())
const devtoolsMarkdownStressWindow = injeca.provide('windows:devtools:markdown-stress', () => setupDevtoolsWindow())
const devtoolsWindow = injeca.provide('windows:devtools', () => setupDevtoolsWindow())
const onboardingWindowManager = injeca.provide('windows:onboarding', {
dependsOn: { serverChannel, i18n, windowAuthManager },
@@ -167,7 +167,7 @@ app.whenReady().then(async () => {
})
const settingsWindow = injeca.provide('windows:settings', {
dependsOn: { widgetsManager, beatSync, autoUpdater, devtoolsMarkdownStressWindow, serverChannel, mcpStdioManager, i18n, windowAuthManager },
dependsOn: { widgetsManager, beatSync, autoUpdater, devtoolsWindow, serverChannel, mcpStdioManager, i18n, windowAuthManager },
build: async ({ dependsOn }) => setupSettingsWindowReusableFunc(dependsOn),
})
@@ -7,54 +7,74 @@ import icon from '../../../../resources/icon.png?asset'
import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location'
import { createReusableWindow } from '../../libs/electron/window-manager'
export interface OpenDevtoolsWindowParams extends Partial<Electron.Rectangle> {
key: string
route?: string
}
export interface DevtoolsWindowManager {
openWindow: (route?: string) => Promise<BrowserWindow>
getWindow: () => Promise<BrowserWindow>
openWindow: (params: OpenDevtoolsWindowParams) => Promise<BrowserWindow>
}
export function setupDevtoolsWindow(): DevtoolsWindowManager {
const rendererBase = baseUrl(resolve(getElectronMainDirname(), '..', 'renderer'))
const defaultRoute = '/devtools/markdown-stress'
let currentRoute = defaultRoute
const defaultRoute = '/devtools'
const reusableWindows = new Map<string, ReturnType<typeof createReusableWindow>>()
const reusable = createReusableWindow(async () => {
const window = new BrowserWindow({
title: 'Devtools',
width: 1020,
height: 720,
minWidth: 640,
minHeight: 480,
show: false,
icon,
webPreferences: {
preload: join(getElectronMainDirname(), '../preload/index.mjs'),
// Preload exposes Electron APIs and needs Node access.
sandbox: false,
},
function getReusableForKey(key: string, route: string) {
const existing = reusableWindows.get(key)
if (existing)
return existing
const reusable = createReusableWindow(async () => {
const window = new BrowserWindow({
title: 'Devtools',
width: 1020,
height: 720,
minWidth: 640,
minHeight: 480,
show: false,
icon,
webPreferences: {
preload: join(getElectronMainDirname(), '../preload/index.mjs'),
// Preload exposes Electron APIs and needs Node access.
sandbox: false,
},
})
window.on('ready-to-show', () => window.show())
window.on('closed', () => {
if (reusableWindows.get(key) === reusable)
reusableWindows.delete(key)
})
window.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
await load(window, withHashRoute(rendererBase, route))
return window
})
window.on('ready-to-show', () => window.show())
window.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
reusableWindows.set(key, reusable)
return reusable
}
await load(window, withHashRoute(rendererBase, currentRoute))
return window
})
async function openWindow(params: OpenDevtoolsWindowParams) {
const targetRoute = params.route ?? defaultRoute
const window = await getReusableForKey(params.key, targetRoute).getWindow()
async function openWindow(route?: string) {
if (route)
currentRoute = route
const window = await reusable.getWindow()
const targetRoute = route ?? currentRoute
const url = withHashRoute(rendererBase, targetRoute)
// If the route changes while the window is open, reload to that route.
const currentUrl = window.webContents.getURL()
if (!currentUrl.includes(`#${targetRoute}`)) {
await load(window, url)
if (params && (params.width !== undefined || params.height !== undefined || params.x !== undefined || params.y !== undefined)) {
const bounds: Partial<Electron.Rectangle> = {}
if (params.width !== undefined)
bounds.width = params.width
if (params.height !== undefined)
bounds.height = params.height
if (params.x !== undefined)
bounds.x = params.x
if (params.y !== undefined)
bounds.y = params.y
window.setBounds(bounds)
}
return window
@@ -62,6 +82,5 @@ export function setupDevtoolsWindow(): DevtoolsWindowManager {
return {
openWindow,
getWindow: reusable.getWindow,
}
}
@@ -27,7 +27,7 @@ export interface SettingsWindowManager {
export function setupSettingsWindowReusableFunc(params: {
widgetsManager: WidgetsWindowManager
autoUpdater: AutoUpdater
devtoolsMarkdownStressWindow: DevtoolsWindowManager
devtoolsWindow: DevtoolsWindowManager
onWindowCreated?: (window: BrowserWindow) => void
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
@@ -67,7 +67,7 @@ export function setupSettingsWindowReusableFunc(params: {
settingsWindow: window,
widgetsManager: params.widgetsManager,
autoUpdater: params.autoUpdater,
devtoolsMarkdownStressWindow: params.devtoolsMarkdownStressWindow,
devtoolsWindow: params.devtoolsWindow,
serverChannel: params.serverChannel,
mcpStdioManager: params.mcpStdioManager,
i18n: params.i18n,
@@ -23,7 +23,7 @@ export async function setupSettingsWindowInvokes(params: {
settingsWindow: BrowserWindow
widgetsManager: WidgetsWindowManager
autoUpdater: AutoUpdater
devtoolsMarkdownStressWindow: DevtoolsWindowManager
devtoolsWindow: DevtoolsWindowManager
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
@@ -45,7 +45,7 @@ export async function setupSettingsWindowInvokes(params: {
defineInvokeHandler(context, electronOpenSettingsDevtools, async () => params.settingsWindow.webContents.openDevTools({ mode: 'detach' }))
defineInvokeHandler(context, electronOpenDevtoolsWindow, async (payload) => {
await params.devtoolsMarkdownStressWindow.openWindow(payload?.route)
await params.devtoolsWindow.openWindow(payload)
})
return context
@@ -113,7 +113,7 @@ const openDevtoolsWindow = useElectronEventaInvoke(electronOpenDevtoolsWindow)
icon="i-solar:code-bold-duotone"
:text="t('tamagotchi.settings.devtools.pages.markdown-stress.title')"
transition="all ease-in-out duration-250"
@click="() => openDevtoolsWindow({ route: '/devtools/markdown-stress' })"
@click="() => openDevtoolsWindow({ key: 'markdown-stress', route: '/devtools/markdown-stress' })"
>
{{ t('tamagotchi.settings.devtools.pages.markdown-stress.title') }}
</ButtonBar>
+10 -1
View File
@@ -22,7 +22,16 @@ export const electronOpenSettings = defineInvokeEventa<void, { route?: string }>
export const electronSettingsNavigate = defineEventa<{ route: string }>('eventa:event:electron:windows:settings:navigate')
export const electronOpenChat = defineInvokeEventa('eventa:invoke:electron:windows:chat:open')
export const electronOpenSettingsDevtools = defineInvokeEventa('eventa:invoke:electron:windows:settings:devtools:open')
export const electronOpenDevtoolsWindow = defineInvokeEventa<void, { route?: string }>('eventa:invoke:electron:windows:devtools:open')
export interface OpenDevtoolsWindowParams {
key: string
route?: string
width?: number
height?: number
x?: number
y?: number
}
export const electronOpenDevtoolsWindow = defineInvokeEventa<void, OpenDevtoolsWindowParams>('eventa:invoke:electron:windows:devtools:open')
export interface ElectronServerChannelConfig {
tlsConfig?: ServerOptions['tlsConfig'] | null